Search code examples
pythonsessionasynchronoustwistedasyncore

Using asyncore to create interactive sessions with client/server model


I am attempting to create a program that allows many clients to connect to 1 server simultaneously. These connections should be interactive on the server side, meaning that I can send requests from the server to the client, after the client has connected.

The following asyncore example code simply replies back with an echo, I need instead of an echo a way to interactively access each session. Somehow background each connection until I decided to interact with it. If I have 100 sessions I would like to chose a particular one or choose all of them or a subset of them to send a command to. Also I am not 100% sure that the asyncore lib is the way to go here, any help is appreciated.

import asyncore
import socket

class EchoHandler(asyncore.dispatcher_with_send):

    def handle_read(self):
        data = self.recv(8192)
        if data:
            self.send(data)

class EchoServer(asyncore.dispatcher):

    def __init__(self, host, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind((host, port))
        self.listen(5)

    def handle_accept(self):
        pair = self.accept()
        if pair is not None:
            sock, addr = pair
            print 'Incoming connection from %s' % repr(addr)
            handler = EchoHandler(sock)

server = EchoServer('localhost', 8080)
asyncore.loop()

Solution

  • Here's a Twisted server:

    import sys
    
    from twisted.internet.task import react
    from twisted.internet.endpoints import serverFromString
    from twisted.internet.defer import Deferred
    from twisted.internet.protocol import Factory
    
    from twisted.protocols.basic import LineReceiver
    
    class HubConnection(LineReceiver, object):
        def __init__(self, hub):
            self.name = b'unknown'
            self.hub = hub
    
        def connectionMade(self):
            self.hub.append(self)
    
        def lineReceived(self, line):
            words = line.split(" ", 1)
            if words[0] == b'identify':
                self.name = words[1]
            else:
                for connection in self.hub:
                    connection.sendLine("<{}> {}".format(
                        self.name, line
                    ).encode("utf-8"))
    
        def connectionLost(self, reason):
            self.hub.remove(self)
    
    def main(reactor, listen="tcp:4321"):
        hub = []
        endpoint = serverFromString(reactor, listen)
        endpoint.listen(Factory.forProtocol(lambda: HubConnection(hub)))
        return Deferred()
    
    react(main, sys.argv[1:])
    

    and command-line client:

    import sys
    
    from twisted.internet.task import react
    from twisted.internet.endpoints import clientFromString
    from twisted.internet.defer import Deferred, inlineCallbacks
    from twisted.internet.protocol import Factory
    from twisted.internet.stdio import StandardIO
    
    from twisted.protocols.basic import LineReceiver
    from twisted.internet.fdesc import setBlocking
    
    class HubClient(LineReceiver):
        def __init__(self, name, output):
            self.name = name
            self.output = output
    
        def lineReceived(self, line):
            self.output.transport.write(line + b"\n")
    
        def connectionMade(self):
            self.sendLine("identify {}".format(self.name).encode("utf-8"))
    
        def say(self, words):
            self.sendLine("say {}".format(words).encode("utf-8"))
    
    class TerminalInput(LineReceiver, object):
        delimiter = "\n"
        hubClient = None
        def lineReceived(self, line):
            if self.hubClient is None:
                self.output.transport.write("Connecting, please wait...\n")
            else:
                self.hubClient.sendLine(line)
    
    @inlineCallbacks
    def main(reactor, name, connect="tcp:localhost:4321"):
        endpoint = clientFromString(reactor, connect)
        terminalInput = TerminalInput()
        StandardIO(terminalInput)
        setBlocking(0)
        hubClient = yield endpoint.connect(
            Factory.forProtocol(lambda: HubClient(name, terminalInput))
        )
        terminalInput.transport.write("Connecting...\n")
        terminalInput.hubClient = hubClient
        terminalInput.transport.write("Connected.\n")
        yield Deferred()
    
    react(main, sys.argv[1:])
    

    which implement a basic chat server. Hopefully the code is fairly self-explanatory; you can run it to test with python hub_server.py in one terminal, python hub_client.py alice in a second and python hub_client.py bob in a third; then type into alice and bob's sessions and you can see what it does.