在Python TCP服务器中正确添加handle()功能的方法是什么?

1 投票
3 回答
3936 浏览
提问于 2025-04-16 14:41

我有这样的代码:

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 个回答

0

这基本上和被接受的答案差不多,但对回调的强调少了一些。其实不管图形用户界面(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。我这样做是因为文档中建议这么做,但实际上可以使用任何处理器——不需要提供setuphandlefinish这些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()
1

这是我用过的有效方法:

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):
        ...
8

把你的处理器改成这样:

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网络库)。另外,你也要注意,很多图形用户界面系统不喜欢在多个线程中同时运行,所以这点要特别留意。

撰写回答