Search code examples
pythontwistedtwisted.internet

What is the best way of handling received messages in Twisted for Python?


I am a noob at Python and seeking some assistance with architecture. Here is my setup: I have a legacy client application written in LiveCode that runs in multiple locations to display synchronized information based on what the server demands. Think of this as a kiosk. This client piece is not going anywhere.

The server application is what I’m rewriting in Python. My goal is to have the server application running constantly, listening for client socket connections, and sending/receiving data to/from these clients. I have successfully passed messages between this LiveCode client application and a python script that uses Twisted for the socket handling, so now I need to start processing those messages. The code looks something like this:

from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor

class MessageListener(LineReceiver):

    def __init__(self, users):
        self.users = users
        self.name = None

    def connectionMade(self):
        d = self.transport.getHost()
        print("Connection established with {}:{}".format(d.host, d.port))

    def connectionLost(self, reason):
        print("Connection lost: {}".format(reason))
        if self.name in self.users:
            del self.users[self.name]

    def dataReceived(self, line):
        d = self.transport.getHost()
        print("Received message from {}:{}...{}".format(d.host, d.port, line))
        self.handle_GOTDATA(line)

    def handle_GOTDATA(self, msg):
        #convert "msg" to string and parse it into the necessary chunks

        #*****Go do something based on the requestor and the command*****
        #Use if-elif or dictionary to determine which function to run
        #based on what the string tells us.
        #Should these functions be defined here or in a separate class?

class MessageListenerFactory(Factory):

    def __init__(self):
        self.users = {} # maps user names to Chat instances

    def buildProtocol(self, addr):
        return MessageListener(self.users)

reactor.listenTCP(50010, MessageListenerFactory())

reactor.run()

A couple of questions:

  1. The handle_GOTDATA() function is where I will take the received message, parse it out into the chunks that tell me what to do with the data, then call a different function depending on what needs to be done with that data. Do I just define all 20 of these functions in this same “MessageListener” class, or do I write a separate class to keep all of these functions? I might get 10 messages at about the same time, and they may need to call the same function, so I wasn’t sure the best architecture approach here.

  2. I also want to build a GUI to interact with the server for some troubleshooting and monitoring on occasion. I’m familiar with Tkinter and it would be fine for this, and I can write the GUI in a separate file and have it connect to the server over a socket as well. But would I use the same socket listener implemented above and just pass it similar messages? Or should I build a separate class and factory to listen for the GUI connections?


Solution

  • If you're going to use LineReceiver, you should not override dataReceived. Instead, override lineReceived. If your protocol isn't line-oriented, you probably don't want to use LineReceiver. Also you may want to consider using the Tubes interface in either case.

    Do I just define all 20 of these functions in this same “MessageListener” class, or do I write a separate class to keep all of these functions?

    You should probably put them on a different class. If you put them on MessageListener then you will have more difficulty testing, refactoring, and maintaining the code because your protocol logic is tightly coupled to your application logic.

    Define an explicit interface that MessageListener uses to dispatch high-level events representing network actions. Then implement that interface appropriately for your particular application. Later, you can implement it differently for another application. Or you can write test doubles. Or you can change your protocol without changing your application logic. Decoupling the two pieces gives you lots of extra flexibility compared to smashing them both into one class.

    I might get 10 messages at about the same time, and they may need to call the same function, so I wasn’t sure the best architecture approach here.

    I think this is orthogonal but if it's an important enough part of your protocol or application logic, you might want to consider building some kind of vectorization into the explicit interface I mentioned above. Instead of appObj.doOneThing(oneThing) perhaps you have appObj.doSeveralThings([oneThing, anotherThing]).

    I also want to build a GUI to interact with the server for some troubleshooting and monitoring on occasion. I’m familiar with Tkinter and it would be fine for this, and I can write the GUI in a separate file and have it connect to the server over a socket as well. But would I use the same socket listener implemented above and just pass it similar messages? Or should I build a separate class and factory to listen for the GUI connections?

    That depends on the interactions you want to perform, I suppose. If this is a GUI with extra privileges compared to the normal client, you need a protocol and server with authentication and authorization functionality or you must not use the same server and protocol because you risk giving clients those extra privileges.

    If you just want to simulate a real client with a kind of debug-friendly interface that lets you easily interact with the server in ways that a client would normally interact with it, but with an interface that makes this easier for you, you can (and presumably should) connect to the same server.