1

I have code like this:

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)

But I don't want to have to use GUI, a global variable, to contain the function that I want to call when a message is received by my server. Instead I want to send the SingleTCPHandler class the GUI object so it can call the function locally instead of referencing a global variable.

Thoughts? Little new to python...

dougvk
  • 3,644
  • 3
  • 20
  • 24
  • Check out this, which seems to be related: http://stackoverflow.com/questions/12233940/passing-extra-metadata-to-a-requesthandler-using-pythons-socketserver-and-child/35364942#35364942 – Blindfreddy Feb 12 '16 at 14:28

3 Answers3

8

Changing your handler to

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)

Somewhere later (where you create the TCP Server):

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

Explanation:

1) handler_factory is called with the callback which should be invoked.

2) handler_factory creates a new function called createHandler. This function will be handed over to ThreadedTCPServer. It invokes it when it needs to create a new handler.

3) When invoked, it creates your ThreadedTCPRequestHandler and hands over the callback parameter (which is show_message_box in this example) to ThreadedTCPRequestHandler.init which stores it. The "magic" behind this is called a "closure". I.e. you can access the callback parameter from the outer function handler_factory within the inner function createHandler. Google for "closure python" and you will get some good explanations.

4) When handle() is finally invoked, it calls self.callback(data). Since self.callback is show_message_box, show_message_box(data) will be called effectively.

There are slightly shorter ways to express this as well:

def my_callback():
    print 'Hello'
server = ThreadedTCPServer((HOST, PORT), lambda *args, **keys: ThreadedTCPRequestHandler(my_callback, *args, **keys))

will work, just as

def my_callback():
    print 'Hello'
import functools
server = ThreadedTCPServer((HOST, PORT), functools.partial(ThreadedTCPRequestHandler, my_callback))

I don't know your specific use case, but if you need to do more stuff with sockets, you might want to look at http://www.twistedmatrix.com (popular python networking package). You should also be aware that a lot of GUI systems don't like to be run concurrently from multiple threads, so you need to watch out for this.

nitrogenycs
  • 962
  • 9
  • 13
1

This is what worked for me:

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):
        ...
Razor Robotics
  • 183
  • 1
  • 9
0

This is basically the same as the accepted answer, but with less emphasis on the callback. It does not matter if GUI is a callback method or whatever. The problem is how to pass an object to the request handler. The solution is indeed a request handler factory, which receives the GUI (the object) and passes it to the handler when it creates it. Some would suggest to pass the object (GUI) through the server, but it is awkward because the server has nothing to do with it. It does not even look at it. A factory is also more flexible - you could even make the handler dependent upon the request !

The factory will have to pass the object (GUI) in the constructor of the handler, so this constructor needs to be extended (I assume Python 3 - with 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))

BTW, currently, there is no need to subclass SocketServer.BaseRequestHandler as discussed in https://bugs.python.org/issue29947 . I do it, because they suggest it in the documentation, but any handler can be used - no need to provide the setup, handle and finish API. There is no constraint on the handler, except that it must handle the request. We only need to define the factory method which, when called by the server, will instantiate the handler (and start it) while passing the GUI object and a connection (the request) to the client:

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)

These are used in this way:

factory =  RequestHandlerFactory(GUI)

with ServerUseFactory((HOST, PORT), factory.start) as server:
    server.serve_forever()