在Python TCP服务器中正确添加handle()功能的方法是什么?
我有这样的代码:
class SingleTCPHandler(SocketServer.BaseRequestHandler):
""" One instance per connection. """
def handle(self):
data = Net.recv_from_socket(self.request)
GUI.run(str("RECV'd message: %s" % data))
class SimpleServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def __init__(self, server_address, RequestHandlerClass):
SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass)
但是我不想使用一个全局变量GUI
来保存我想在服务器收到消息时调用的函数。相反,我想把SingleTCPHandler
类的GUI
对象传过去,这样它就可以在本地调用这个函数,而不是引用一个全局变量。
大家有什么想法?我对Python还不太熟悉……
3 个回答
这基本上和被接受的答案差不多,但对回调的强调少了一些。其实不管图形用户界面(GUI)是回调方法还是其他什么,关键在于怎么把一个对象传给请求处理器。解决方案就是用一个请求处理器工厂,它接收这个GUI对象,并在创建处理器时把它传进去。有些人可能会建议通过服务器来传这个对象(GUI),但这样做很尴尬,因为服务器根本不关心这个对象,甚至都不会看它。使用工厂的方式更灵活——你甚至可以让处理器根据请求的不同而有所不同!
工厂需要在处理器的构造函数中传入这个对象(GUI),所以这个构造函数需要扩展(我假设使用的是Python 3 - 用super()):
class SingleTCPHandler(SocketServer.BaseRequestHandler):
""" One instance per connection. """
def __init__(self, request, client_address, server, GUI):
# The GUI must be passed before we call the parent constructor
# because it actually handles the request and finishes it.
self._GUI = GUI
super().__init__(request, client_address, server)
def handle(self):
data = Net.recv_from_socket(self.request)
self._GUI.run(str("RECV'd message: %s" % data))
顺便说一下,目前不需要像在https://bugs.python.org/issue29947中讨论的那样去子类化SocketServer.BaseRequestHandler
。我这样做是因为文档中建议这么做,但实际上可以使用任何处理器——不需要提供setup
、handle
和finish
这些API。对处理器没有特别的限制,只要它能处理请求就行。我们只需要定义一个工厂方法,当服务器调用它时,会实例化处理器(并启动它),同时把GUI对象和与客户端的连接(即request
)传进去:
class RequestHandlerFactory:
def __init__(self, GUI):
self._GUI = GUI
def start(self, request, client_address, server):
# The factory could even pass a different handler at each request!
# The application could change the handler while the server is running!
return SingleTCPHandler(self, request, client_address, server, self._GUI)
这些是这样使用的:
factory = RequestHandlerFactory(GUI)
with ServerUseFactory((HOST, PORT), factory.start) as server:
server.serve_forever()
这是我用过的有效方法:
class MyServer():
def __init__(self, myGui, host='', port=8875):
myServer = socketserver.TCPServer((host, port), functools.partial(MyHandler, myGui))
myServer.serve_forever()
class MyHandler(socketserver.BaseRequestHandler):
def __init__(self, myGui, socket, client_address, tcpServer):
self.myGui = myGui
super().__init__(socket, client_address, tcpServer)
def handle(self):
...
把你的处理器改成这样:
class SingleTCPHandler(SocketServer.BaseRequestHandler):
""" One instance per connection. """
def __init__(self, callback, *args, **keys):
self.callback = callback
SocketServer.BaseRequestHandler.__init__(self, *args, **keys)
def handle(self):
data = Net.recv_from_socket(self.request)
self.callback(data)
然后在你创建TCP服务器的地方:
def show_message_box(data):
GUI.run(str("RECV'd message: %s" % data))
def handler_factory(callback):
def createHandler(*args, **keys):
return ThreadedTCPRequestHandler(callback, *args, **keys)
return createHandler
server = ThreadedTCPServer((HOST, PORT), handler_factory(show_message_box))
解释一下:
1) handler_factory这个函数会被调用,并传入一个需要执行的回调函数。
2) handler_factory会创建一个新的函数,叫做createHandler。这个函数会交给ThreadedTCPServer使用。当服务器需要创建新的处理器时,就会调用这个函数。
3) 当createHandler被调用时,它会创建你的ThreadedTCPRequestHandler,并把回调参数(在这个例子中是show_message_box)传给ThreadedTCPRequestHandler的init方法,这个方法会把它存起来。这里面的“魔法”叫做“闭包”。也就是说,你可以在内部函数createHandler中访问外部函数handler_factory的回调参数。你可以在网上搜索“closure python”,会找到很多不错的解释。
4) 当handle()最终被调用时,它会执行self.callback(data)。由于self.callback是show_message_box,所以实际上会调用show_message_box(data)。
还有一些更简短的写法:
def my_callback():
print 'Hello'
server = ThreadedTCPServer((HOST, PORT), lambda *args, **keys: ThreadedTCPRequestHandler(my_callback, *args, **keys))
也可以这样工作,就像:
def my_callback():
print 'Hello'
import functools
server = ThreadedTCPServer((HOST, PORT), functools.partial(ThreadedTCPRequestHandler, my_callback))
我不知道你具体的使用场景,但如果你需要处理更多的socket相关的事情,可以看看http://www.twistedmatrix.com(一个流行的Python网络库)。另外,你也要注意,很多图形用户界面系统不喜欢在多个线程中同时运行,所以这点要特别留意。