Search code examples
pythonasynchronoustcpprotocolstwisted

Send message to existing TCP connection using Twisted


I am writing a TCP server to listen for TCP packets containing status information from remote machines. The remote machines keep the TCP connection alive once established. Here are the salient parts of my code:

#!/usr/bin/python
from twisted.internet import reactor, protocol
class FactoryProcess(protocol.Protocol):
    def dataReceived(self, data):
        # Process received data
    def send_data(self, message):
        # Reply to message etc
        self.transport.write(message)
factory = protocol.ServerFactory()
factory.protocol = FactoryProcess
reactor.listenTCP(8256,factory)
reactor.run()

The machines can connect and send their data, and I can send acknowledgements back in the send_data block. So far, so good. I cannot understand how to asynchronously send data to one of the devices from outside the Protocol code. Clearly, I need to somehow access an instance of the Factory class for the specific connection I wish to use but I cannot see how. Keep safe and many thanks.

EDIT After @notorious.no provided a very helpful example, I changed my code to capture IP addresses and ports, also connection objects of connected devices:

from twisted.internet import endpoints, protocol, reactor

device_ips = []
device_ports = []
connections = []

class ChatProtocol(protocol.Protocol):
    def connectionMade(self):
        global device_ips, device_ports, connections
        # Append client
        self.factory.clientList.append(self)
        print('client connected. Connection Count = ' + str(len(self.factory.clientList)))
        connections.append(self)
        ip, port = self.transport.client
        device_ips.append(ip)
        device_ports.append(port)
        print('ips:' + str(device_ips) + ', ports:' + str(device_ports) + ', connections:' + str(connections))


    def connectionLost(self, _):
        # Remove client
        self.factory.clientList.remove(self)
        print('client lost. Connection Count = ' + str(len(self.factory.clientList)))

    def dataReceived(self, data):
        print('Data received:' + str(data))
        # Send message to all connected clients
        for client in self.factory.clientList:
            if client == self:
                continue

            client.transport.write(data)

class ChatFactory(protocol.Factory):
    protocol = ChatProtocol
    clientList = []

def main():
    epServer = endpoints.serverFromString(reactor, "tcp:8123")
    epServer.listen(ChatFactory())
    reactor.run()

main()

When I run this and then connect two test devices I get:

client connected. Connection Count = 1
ips:['redacted'], ports:[54182], connections:[<__main__.ChatProtocol instance at 0x7f5a835afcd0>]
client connected. Connection Count = 2
ips:['redacted', 'redacted'], ports:[54182, 57437], connections:[<__main__.ChatProtocol instance at 0x7f5a835afcd0>, <__main__.ChatProtocol instance at 0x7f5a835c2140>]

So now I have lists of connected device IPs and ports, and presumably I can use the connections objects to asynchronously send one a message when needed. Please could you advise how I can do this? Keep safe...


Solution

  • Not really sure what you mean by "devices from outside the Protocol code" but I assume you mean accessing other clients that have connected to the same server (please comment if that's not the case). One thing you can do is have list of connected protocols in the factory object. The Factory.buildProtocol (by default, unless you overload it) will set the factory param in the protocol.

    from twisted.internet import endpoints, protocol, reactor
    
    class ChatProtocol(protocol.Protocol):
        def connectionMade(self):
            # Append client
            self.factory.clientList.append(self)
            print(len(self.factory.clientList))
    
        def connectionLost(self, _):
            # Remove client
            self.factory.clientList.remove(self)
            print(len(self.factory.clientList))
    
        def dataReceived(self, data):
            # Send message to all connected clients
            for client in self.factory.clientList:
                if client == self:
                    continue
    
                client.transport.write(data)
    
    class ChatFactory(protocol.Factory):
        protocol = ChatProtocol
        clientList = []
    
    def main():
        epServer = endpoints.serverFromString(reactor, "tcp:8256:interface=0.0.0.0")
        epServer.listen(ChatFactory())
        reactor.run()
    
    main()