我试图用Pyro来控制一台奴隶机器。我rsync必要的python文件,启动Pyro服务器,通过远程控制执行一些操作,然后我想告诉Pyro服务器关闭。
我无法让Pryo Daemon彻底关闭。它会在Daemon.close()
调用中挂起,或者如果我在没有正确关闭其套接字的情况下注释掉它,那么如果我过早地重新启动服务器则会导致socket.error: [Errno 98] Address already in use
。
它不认为SO_REUSEADDR是正确的修复,因为不洁的套接字关闭仍会导致套接字在TIME_WAIT状态中徘徊,可能导致某些客户端遇到问题。我认为更好的解决方案是说服Pyro Daemon正确关闭其套接字。
从守护程序本身调用Daemon.shutdown()是不合适的吗?
如果我启动服务器然后按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'
答案 0 :(得分:5)
我认为通过让shutdown()
调用守护进程的shutdown
,可以在不使用timeout或loopCondition的情况下完成此操作。根据{{3}}:
另一种可能性是在正在运行的bdaemon对象上调用Pyro4.core.Daemon.shutdown()。这也将突破请求循环并允许您的代码在其自身之后整齐地清理,并且还可以在没有任何其他要求的情况下在线程服务器类型上工作。
以下适用于Windows上的Python3.4.2。此处不需要@Pyro4.oneway
的{{1}}装饰器,但在某些情况下会这样。
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
答案 1 :(得分:0)
我认为我接近解决方案:将loopCondition
参数与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
错误。
我可以找到解决此问题的唯一方法是使服务器COMMTIMEOUT更长(或在调用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}}
我认为这已经足够好了,但考虑到时间安排和网络延迟的不公平,让竞争条件潜伏仍然令人失望。