如何通过客户端请求干净地退出Pyro Daemon?

时间:2014-06-27 21:40:07

标签: python sockets pyro

我试图用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'

2 个答案:

答案 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}}

我认为这已经足够好了,但考虑到时间安排和网络延迟的不公平,让竞争条件潜伏仍然令人失望。