Python – как перезапустить приложение «на лету», когда приложение имеет порт TCP в режиме прослушивания?

Каков наилучший способ перезапуска приложения, в котором он запускал прослушивающий TCP-порт? Проблема заключается в следующем: если я быстро запустил приложение, поскольку перезапуск его завершился неудачно, потому что прослушиваемый сокет уже используется.

Как безопасно перезапустить в таком случае?

socket.error: [Errno 98] Address already in use 

Код:

 #!/usr/bin/python import sys,os import pygtk, gtk, gobject import socket, datetime, threading import ConfigParser import urllib2 import subprocess def server(host, port): sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((host, port)) sock.listen(1) print "Listening... " gobject.io_add_watch(sock, gobject.IO_IN, listener) def listener(sock, *args): conn, addr = sock.accept() print "Connected" gobject.io_add_watch(conn, gobject.IO_IN, handler) return True def handler(conn, *args): line = conn.recv(4096) if not len(line): print "Connection closed." return False else: print line if line.startswith("unittest"): subprocess.call("/var/tmp/runme.sh", shell=True) else: print "not ok" return True server('localhost', 8080) gobject.MainLoop().run() 

runme.sh

 #!/bin/bash ps aux | grep py.py | awk '{print $2}' | xargs kill -9; export DISPLAY=:0.0 && lsof -i tcp:58888 | grep LISTEN | awk '{print $2}' | xargs kill -9; export DISPLAY=:0.0 && java -cp Something.jar System.V & export DISPLAY=:0.0 && /var/tmp/py.py & 

EDIT: Обратите внимание, что я использую Java и Python вместе как одно приложение с двумя уровнями. Итак, runme.sh – это мой сценарий запуска, который запускает оба приложения одновременно. Из Java я нажимаю кнопку перезапуска Python. Но Python не перезапускает, потому что убийство выполняется через BASH.

7 Solutions collect form web for “Python – как перезапустить приложение «на лету», когда приложение имеет порт TCP в режиме прослушивания?”

Вам нужно будет найти эквивалент Python для установки SO_REUSEADDR в сокете, прежде чем связывать его. Обеспечение закрытия розетки при выходе, как рекомендовано в других ответах, не является ни необходимым, ни достаточным, поскольку (а) розетки закрываются ОС при выходе из процесса и (б) вам все равно придется преодолевать принятые соединения в TIME_WAIT , что только SO_REUSEADDR может делать.

1.

У вас есть проблема с убийством вашего питона

 air:~ dima$ ps aux | grep i-dont-exist.py | awk '{print $2}' 34198 

Это означает, что ваш процесс grep попадает в вашу логику перезапуска и убивается.

В Linux вы можете использовать pidof вместо этого.

В качестве альтернативы используйте start-stop-daemon и pid-файл.

2.

Вы уже используете адрес, поэтому я предполагаю, что ваш питон не умирает достаточно быстро.

Для быстрого теста добавьте сон перед повторным запуском python.

Если это помогает, добавьте цикл ожидания ожидания после команды kill и только запустите новый python, когда вы уверены, что старый python больше не работает.

Есть ли вероятность, что ваша программа Python запускает другие процессы? например, через fork, subprocess или os.system?

Возможно, что ваш дескриптор файла прослушивания наследуется порожденным процессом:

os.system ("sleep 1000") # без сокетов:

 ls -l /proc/`pidof sleep`/fd total 0 lrwx------ 1 user user 64 2012-12-19 19:52 0 -> /dev/pts/0 lrwx------ 1 user user 64 2012-12-19 19:52 1 -> /dev/pts/0 l-wx------ 1 user user 64 2012-12-19 19:52 13 -> /dev/null lrwx------ 1 user user 64 2012-12-19 19:52 2 -> /dev/pts/0 

разъем(); setsockopt (); связывания (); Слушать(); os.system («sleep 1000») # с сокетами:

 ls -l /proc/`pidof sleep`/fd total 0 lrwx------ 1 user user 64 2012-12-19 19:49 0 -> /dev/pts/0 lrwx------ 1 user user 64 2012-12-19 19:49 1 -> /dev/pts/0 l-wx------ 1 user user 64 2012-12-19 19:49 13 -> /dev/null lrwx------ 1 user user 64 2012-12-19 19:49 2 -> /dev/pts/0 lrwx------ 1 user user 64 2012-12-19 19:49 5 -> socket:[238967] lrwx------ 1 user user 64 2012-12-19 19:49 6 -> socket:[238969] 

Возможно, ваш скрипт Python умер, но его дети этого не сделали, последние поддерживают ссылку на прослушивающий сокет, и поэтому новый процесс Python не может связываться с одним и тем же адресом.

Вот мое предположение: kill асинхронно. Он просто сообщает ядру отправить сигнал процессу, он также не дожидается, когда сигнал будет доставлен и обработан. Перед перезапуском процесса вы должны использовать команду wait.

 $ wait $PID 

Вы можете добавить больше логики в сценарий запуска, чтобы выполнить предварительное тестирование и очистку.

 #!/bin/bash export DISPLAY=:0.0 # If py.py is found running if pgrep py.py; then for n in $(seq 1 9); do # kill py.py starting at kill -1 and increase to kill -9 if ! pgrep py.py; then # if no running py.py is found break out of this loop break fi pkill -${n} py.py sleep .5 done fi # Verify nothing has tcp/58888 open in a listening state if lsof -t -i tcp:58888 -stcp:listen; then echo process with pid $(lsof -t -i tcp:58888 -stcp:listen) still listening on port 58888, exiting exit fi java -cp Something.jar System.V & /var/tmp/py.py & 

В конце концов вы, вероятно, захотите использовать полномасштабный сценарий инициализации и демонзировать эти процессы. См. http://www.thegeekstuff.com/2012/03/lsbinit-script/, например, если ваши процессы запущены как непривилегированный пользователь, который немного изменит реализацию, но общие концепции будут одинаковыми.

Возможное решение №1: Fork и выполнить новую копию скрипта python из старого. Он унаследует прослушивающий сокет. Затем, при желании, отсоедините его от родителя и убейте (или выйдите) из родителя. Обратите внимание, что родительский (старая версия) может завершить обслуживание любых существующих запросов, даже если ребенок (новая версия) обрабатывает все новые входящие запросы.

Возможное решение №2: Сигнал старого сценария запуска передать сокет новому сценарию с sendmsg() и SCM_RIGHTS , а затем убить старый скрипт. Этот пример кода говорит о «дескрипторах файлов», но отлично работает с сокетами. См.: Как передать прослушивающий сокет TCP с минимальным временем простоя?

Возможное решение # 3: Если bind() возвращает EADDRINUSE, подождите некоторое время и повторите попытку, пока это не удастся. Если вам нужно быстро перезапустить скрипт и без простоя между ними, это, конечно, не сработает 🙂

Возможное решение №4: не убивайте свой процесс с помощью kill -9. Убейте его другим сигналом, например SIGTERM . Поймайте SIGTERM и вызовите gobject.MainLoop.quit() когда получите это.

Возможное решение # 5: Убедитесь, что родительский процесс вашего скрипта python (например, оболочки) wait на нем. Если родительский процесс скрипта не запущен, или если сценарий демонанизирован, то, если его убили с помощью SIGKILL , init станет его родителем. вызовы init периодически wait но это может занять немного времени, возможно, это то, что вы используете. Если вы должны использовать SIGKILL но вы хотите, чтобы более быстрая очистка просто позвонила, wait .

Решения 4 и 5 имеют очень короткое, но отличное от нуля время между остановкой старого сценария и запуском нового. Решение 3 имеет потенциально значительное время между ними, но очень просто. Решения 1 и 2 – это способы сделать это буквально без простоя : любой вызов соединения будет успешным и получит либо старый, либо новый скрипт.

PS Подробнее о поведении SO_REUSEADDR на разных платформах: SO_REUSEADDR не имеет такой же семантики в Windows, как в Unix

Однако в Windows эта опция фактически означает нечто совершенно другое. Это означает, что адрес должен быть украден из любого процесса, который в настоящее время используется на нем.

Я не уверен, что это то, что вы используете, но обратите внимание, что, как описано здесь, поведение в разных версиях Unix также несколько отличается.

Что бы я ни старался, это не сработало. Поэтому, чтобы уменьшить риск, я начал использовать файловую систему в качестве примера сокета:

 # Echo server program import socket,os s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: os.remove("/tmp/socketname") except OSError: pass s.bind("/tmp/socketname") s.listen(1) conn, addr = s.accept() while 1: data = conn.recv(1024) if not data: break conn.send(data) conn.close() # Echo client program import socket s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect("/tmp/socketname") s.send('Hello, world') data = s.recv(1024) s.close() print 'Received', repr(data) 
  • SocketServer: избавиться от ' Адрес уже используется'
  • отправка данных как объекта JSON из Python в Javascript с Jinja
  • путать с документом numpy.c_ и примером кода
  • Использование super () в методе setter свойства при использовании декоратора @property вызывает AttributeError
  • упорядочивая перетасованные точки, которые могут быть соединены для формирования многоугольника (в python)
  • Функция лямбда-функции Python для вычисления факториала числа
  • Python raw_input использует TAB вместо ENTER?
  • Python web scraping, считая появление списка слов на каждой странице
  • Изменение значения в одном списке изменяет значения в другом списке с другим идентификатором памяти
  • Как удалить пробелы из строки в Python?
  • Как я могу получить последнее модифицированное время с помощью python3 urllib?
  • Python - лучший язык программирования в мире.