#python #sockets #pyro
#python #розетки #pyro
Вопрос:
Я пытаюсь использовать Pyro для управления подчиненной машиной. Я повторно синхронизирую необходимые файлы python, запускаю сервер Pyro, выполняю некоторые действия с помощью удаленного управления, а затем я хочу сообщить серверу Pyro о завершении работы.
У меня возникли проблемы с завершением работы демона Pryo. Он либо зависает во время Daemon.close()
вызова, либо, если я закомментирую эту строку, он завершается без корректного завершения работы сокета, что приведет к socket.error: [Errno 98] Address already in use
тому, что я перезапущу сервер слишком рано.
Я не думаю, что SO_REUSEADDR является правильным решением, поскольку нечистое завершение работы сокета по-прежнему приводит к зависанию сокета в состоянии TIME_WAIT, что потенциально может вызвать проблемы у некоторых клиентов. Я думаю, что лучшее решение — убедить демона Pyro правильно закрыть свой сокет.
Неправильно ли вызывать Daemon.завершение работы() изнутри самого демона?
Если я запускаю сервер, а затем нажимаю CTRL-C без каких-либо подключенных клиентов, у меня нет никаких проблем ( Address already in use
ошибок нет). Это делает возможным чистое завершение работы в большинстве случаев (при условии, что в остальном клиент и сервер в здравом уме).
Пример: server.py
import Pyro4
class TestAPI:
def __init__(self, daemon):
self.daemon = daemon
def hello(self, msg):
print 'client said {}'.format(msg)
return 'hola'
def shutdown(self):
print 'shutting down...'
self.daemon.shutdown()
if __name__ == '__main__':
daemon = Pyro4.Daemon(port=9999)
tapi = TestAPI(daemon)
uri = daemon.register(tapi, objectId='TestAPI')
daemon.requestLoop()
print 'exited requestLoop'
daemon.close() # this hangs
print 'daemon closed'
Пример: client.py
import Pyro4
if __name__ == '__main__':
uri = 'PYRO:TestAPI@localhost:9999'
remote = Pyro4.Proxy(uri)
response = remote.hello('hello')
print 'server said {}'.format(response)
try:
remote.shutdown()
except Pyro4.errors.ConnectionClosedError:
pass
print 'client exiting'
Комментарии:
1. Привет, Эрик. У меня никогда не было
Address already in use
для сервера Pyro, но я все время получаю его дляName Server
. Нажатие CTRL C на сервере имен имеет 50%-ную вероятность возникновения этой ошибки, если я снова запущу сервер имен в течение 30 секунд. У вас уже было такое раньше?
Ответ №1:
Я думаю, что это можно сделать без использования тайм-аута или условия цикла, если вы shutdown()
вызовете демона shutdown
. Согласно http://pythonhosted.org/Pyro4/servercode.html#cleaning-up:
Другой возможностью является вызов Pyro4.core .Демон.завершение работы () для запущенного объекта bdaemon. Это также выйдет из цикла запроса и позволит вашему коду аккуратно убираться после себя, а также будет работать на потоковом сервере без каких-либо других требований.
Следующее работает на Python3.4.2 в Windows. @Pyro4.oneway
Декоратор для shutdown
здесь не нужен, но в некоторых ситуациях он нужен.
server.py
import Pyro4
# using Python3.4.2
@Pyro4.expose
class TestAPI:
def __init__(self, daemon):
self.daemon = daemon
def hello(self, msg):
print('client said {}'.format(msg))
return 'hola'
@Pyro4.oneway # in case call returns much later than daemon.shutdown
def shutdown(self):
print('shutting down...')
self.daemon.shutdown()
if __name__ == '__main__':
daemon = Pyro4.Daemon(port=9999)
tapi = TestAPI(daemon)
uri = daemon.register(tapi, objectId='TestAPI')
daemon.requestLoop()
print('exited requestLoop')
daemon.close()
print('daemon closed')
client.py
import Pyro4
# using Python3.4.2
if __name__ == '__main__':
uri = 'PYRO:TestAPI@localhost:9999'
remote = Pyro4.Proxy(uri)
response = remote.hello('hello')
print('server said {}'.format(response))
remote.shutdown()
remote._pyroRelease()
print('client exiting')
Ответ №2:
Я думаю, что я близок к решению: комбинация использования loopCondition
параметра to requestloop()
и значения конфигурации COMMTIMEOUT
.
server.py
import Pyro4
Pyro4.config.COMMTIMEOUT = 1.0 # without this daemon.close() hangs
class TestAPI:
def __init__(self, daemon):
self.daemon = daemon
self.running = True
def hello(self, msg):
print 'client said {}'.format(msg)
return 'hola'
def shutdown(self):
print 'shutting down...'
self.running = False
if __name__ == '__main__':
daemon = Pyro4.Daemon(port=9999)
tapi = TestAPI(daemon)
uri = daemon.register(tapi, objectId='TestAPI')
def checkshutdown():
return tapi.running
daemon.requestLoop(loopCondition=checkshutdown) # permits self-shutdown
print 'exited requestLoop'
daemon.close()
print 'daemon closed'
К сожалению, есть одно условие, при котором он по-прежнему оставляет сокет в состоянии TIME_WAIT. Если клиент закрывает свой сокет после сервера, то следующая попытка запустить сервер возвращает ту же Address already in use
ошибку.
Единственный способ, который я могу найти, чтобы обойти это, — увеличить время ожидания сервера (или перевести его в режим ожидания на несколько секунд перед вызовом daemon.close()
) и убедиться, что клиент всегда звонит _pyroRelease()
сразу после завершения вызова:
client.py
import Pyro4
if __name__ == '__main__':
uri = 'PYRO:TestAPI@localhost:9999'
remote = Pyro4.Proxy(uri)
response = remote.hello('hello')
print 'server said {}'.format(response)
remote.shutdown()
remote._pyroRelease()
print 'client exiting'
Я полагаю, что этого достаточно, но, учитывая несправедливость планирования и задержки в сети, по-прежнему разочаровывает, что это условие гонки скрывается.
Комментарии:
1. В ходе тестирования я обнаружил, что слишком агрессивное использование COMMTIMEOUT приводит к ложным сбоям, поэтому мне пришлось отложить это до 5 секунд. Еще одна причина, по которой это решение кажется не совсем правильным.