如何通过客户端请求干净地退出Pyro守护进程?

6 投票
2 回答
3224 浏览
提问于 2025-04-18 11:23

我正在尝试使用Pyro来控制一个从属机器。我把需要的Python文件通过rsync同步过去,启动了一个Pyro服务器,进行了一些远程控制的操作,然后我想告诉Pyro服务器关闭。

我在让Pyro守护进程正常关闭时遇到了麻烦。它要么在Daemon.close()这个调用上卡住,要么如果我把这一行注释掉,它就会在没有正确关闭套接字的情况下退出,这样如果我太快重启服务器,就会出现socket.error: [Errno 98] Address already in use的错误。

我觉得SO_REUSEADDR并不是正确的解决办法,因为不干净的套接字关闭仍然会导致套接字停留在TIME_WAIT状态,这可能会让一些客户端遇到问题。我认为更好的解决方案是让Pyro守护进程正确关闭它的套接字。

在守护进程内部调用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

我觉得我快找到解决办法了:就是把 requestloop() 里的 loopCondition 参数和配置值 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'

我想这样也算可以,但考虑到调度的不公平性和网络延迟,还是让人失望有这样的竞争条件存在。

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')

撰写回答