6

我正在尝试使用 Pyro 来控制从机。我 rsync 必要的 python 文件,启动一个 Pyro 服务器,通过远程控制执行一些操作,然后我想告诉 Pyro 服务器关闭。

我无法让 Pryo 守护进程干净地关闭。它要么挂在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'
4

2 回答 2

6

我认为这可以通过shutdown()调用守护进程的shutdown. 根据http://pythonhosted.org/Pyro4/servercode.html#cleaning-up

另一种可能性是在运行的 bdaemon 对象上调用 Pyro4.core.Daemon.shutdown()。这也将打破请求循环并允许您的代码在其自身之后整齐地清理,并且还将在没有任何其他要求的线程服务器类型上工作。

以下适用于 Windows 上的 Python3.4.2。这里不需要@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')
于 2016-12-27T23:52:23.740 回答
0

我想我接近一个解决方案:结合使用loopCondition参数 torequestloop()和 config value 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'

我想这已经足够了,但考虑到调度的不公平性和网络延迟,让这种竞争条件潜伏仍然令人失望。

于 2014-06-27T23:04:53.347 回答