Search code examples
pythonprotocolstwistedfactoryreactor

How to modify variables from twisted Protocol methodes such as ConnectionMade


I'm new to python and much more to Twisted, so sorry if the title is not clear enough, but I'll do my best to describe my problem.

I want to separate the logic and networking parts in my application, so I have 2 classes Controller and SimpleServer, I have a variable named nodes in Controller which I want to update by adding the Client Ip of each connection, It's probably obvious for python experts, but I can't get it. Please, take a look at my code. I commented the line where I'm stuck.

Thanks for any help

controller.py

class Controller(object):
    nodes = []
    def __init__(self, port):
        ss= SimpleServer(port)

server.py

class MyServerProtocol(Protocol):
    def connectionMade(self): 
        #ctrl.nodeList.append(self.transport.getPeer()) #this is what I want to do

class ControllerFactory(Factory):
    def buildProtocol(self, addr):
        return MyServerProtocol()

class SimpleServer():
    def __init__(self, port):
        reactor.listenTCP(port, ControllerFactory()) #@UndefinedVariable
        print 'server listening on %d' %port
        reactor.run() #@UndefinedVariable

Main Program

if __name__ == '__main__':
    port = 9123
    ctrl = Controller(port)

Client Side

class myClientProtocol(Protocol):
    def dataReceived(self, data):
        print ("data received", data)

class myClientFactory(ClientFactory):
    def buildProtocol(self, addr):
        print 'Connected.'
        return myClientProtocol()

if __name__ == '__main__':

    print "CLIENT : connecting to : [ %s ] on [ %s ]\n" %(IP,PORT)
    reactor.connectTCP('127.0.0.1',9123, myClientFactory()) #@UndefinedVariable
    reactor.run() #@UndefinedVariable    

Solution

  • You just need references to the right objects.

    Just like you needed a reference to the reactor so you imported twisted.internet.reactor so you could call the run method.

    controller.py

    class Controller(object):
        def __init__(self, port):
            # Don't set self.nodes as a class attribute or it is shared across all instances
            self.nodes = [] 
            # Don't just use a local for SimpleServer, keep a reference on an attribute.  Also, pass the controller instance in.
            self.ss = SimpleServer(port, self)
    

    server.py

    class MyServerProtocol(Protocol):
        def connectionMade(self): 
            # Get the reference to the Controller instance.
            ctrl = self.factory.server.controller
            # Use it.
            ctrl.nodeList.append(self.transport.getPeer())    
    
    class ControllerFactory(Factory):
        # Set this class attribute so the inherited buildProtocol does the work for us, including setting the factory attribute.
        protocol = MyServerProtocol
        def __init__(self, server):
            # Save a reference to the given server as an attribute on the instance.
            self.server = server
            Factory.__init__(self)
    
    class SimpleServer():
        def __init__(self, port, controller):
            self.controller = controller
            # Pass a reference to the SimpleServer instance to the ControllerFactory
            reactor.listenTCP(port, ControllerFactory(self))
            print 'server listening on %d' %port
            reactor.run()
    

    Main Program

    if __name__ == '__main__':
        port = 9123
        ctrl = Controller(port)
    

    Client Side

    class myClientProtocol(Protocol):
        def dataReceived(self, data):
            print ("data received", data)
    
    class myClientFactory(ClientFactory):
        def buildProtocol(self, addr):
            print 'Connected.'
            return myClientProtocol()
    
    if __name__ == '__main__':
    
        print "CLIENT : connecting to : [ %s ] on [ %s ]\n" %(IP,PORT)
        reactor.connectTCP('127.0.0.1',9123, myClientFactory()) #@UndefinedVariable
        reactor.run() #@UndefinedVariable    
    

    I've made the smallest set of changes I can see to demonstrate the solution. I haven't tested the resulting code. Hopefully, you can see that what I've done is take references to the objects that one part of your code has and pass them to other parts of your code that needs them. This frequently takes the form of passing arguments to functions (initializer, methods, whatever) and setting attributes on objects (generally whatever self refers to).

    I haven't tried to improve the maintainability of the code or correct any fraught patterns. This code is still not a model of ideal software but it should at least let you append a value to the list you want it appended to.

    Some other suggestions I would make are:

    • Use more descriptive names
    • Use less indirection
    • Don't make __init__ have side-effects other than setting attributes on self
    • Don't violate the law of Demeter so hard
    • Don't do whatever you're doing (probably a * import?) that caused you to sprinkle #@UndefinedVariable throughout.