Base Server

Basic asynchronous chat server.

This server only redirect messages received from a client to all other clients.

Parameters

Name Type Description Default
host str host or ip to run the server on 'localhost'
port int port to run the server on 1234

Example

    server = BaseServer(host='localhost', port=1234)
    server.run()

on_new_client(self, client_reader, client_writer)

Show source code in base/server.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
    async def on_new_client(
        self,
        client_reader: asyncio.streams.StreamReader,
        client_writer: asyncio.streams.StreamWriter,
    ) -> None:
        """
        Register a newly connected client.

        This method

         - set an id to the client
         - register the client in `self.clients`
         - wait for client messages and dispatch them to all other clients

        Arguments:
            client_reader: reader object to read client messages from
            client_writer: writer object used to send messages to the client

        !!! tip
            if the client send `:q` as a message, it will be disconnected.

        """
        logger.debug("New client connected.")
        client_id = self.register_client(client_writer=client_writer)
        while True:
            new_message = (await client_reader.read(255)).decode()
            logger.debug("New message received: %s" % new_message)
            if not new_message or new_message == ":q":
                logger.info("Disconnect client %s" % client_id)
                break
            await self.on_new_message(from_client_id=client_id, message=new_message)
        client_writer.close()
        del self.clients[client_id]
        logger.info("Nb connected clients: %s" % len(self.clients.keys()))

Register a newly connected client.

This method

  • set an id to the client
  • register the client in self.clients
  • wait for client messages and dispatch them to all other clients

Parameters

Name Type Description Default
client_reader StreamReader reader object to read client messages from required
client_writer StreamWriter writer object used to send messages to the client required

Tip

if the client send :q as a message, it will be disconnected.

on_new_message(self, from_client_id, message)

Show source code in base/server.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    async def on_new_message(self, from_client_id: str, message: str) -> None:
        """
        Send a message to all connected clients.

        Arguments:
            from_client_id: client id defined in `BaseServer.on_new_client`
            message: message to send to all clients

        """
        logger.debug('Dispatch from %s to all: "%s"' % (from_client_id, message,))
        # add \n after message to allow client to use readline (ie read received
        # message one by one)
        message = json.dumps({"from": from_client_id, "message": message}) + "\n"
        for client_id, client in self.clients.items():
            logger.debug('Send message to %s: "%s"' % (client_id, message,))
            await self.send_message(
                client_id=client_id, message=message,
            )

Send a message to all connected clients.

Parameters

Name Type Description Default
from_client_id str client id defined in BaseServer.on_new_client required
message str message to send to all clients required

register_client(self, client_writer)

Show source code in base/server.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    def register_client(self, client_writer: asyncio.streams.StreamWriter) -> str:
        """
        Register a new client in `self.clients`

        Arguments:
            client_writer: client writer object

        Returns:
            client_id as a random 4 char string.

        """
        client_id = str(uuid.uuid4())[:4]
        self.clients[client_id] = {"writer": client_writer}
        return client_id

Register a new client in self.clients

Parameters

Name Type Description Default
client_writer StreamWriter client writer object required

Returns

Type Description
str client_id as a random 4 char string.

run(self)

Show source code in base/server.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    def run(self) -> None:
        """
        Run client by starting an event loop that listen to user input and
        server messages.
        """
        try:
            loop = asyncio.get_event_loop()
            loop.create_task(
                asyncio.start_server(
                    client_connected_cb=self.on_new_client,
                    host=self.host,
                    port=self.port,
                    loop=loop,
                )
            )
            loop.run_forever()
        except KeyboardInterrupt:
            logger.warning("Server closed manually")
        finally:
            logger.info("Close connection to all clients")
            for client_id, client in self.clients.items():
                client["writer"].close()

Run client by starting an event loop that listen to user input and server messages.

send_message(self, message, client_id)

Show source code in base/server.py
33
34
35
36
37
38
39
40
41
42
    async def send_message(self, message: str, client_id: str) -> None:
        """
        Send a message to a client.

        Arguments:
            message: message to send
            client_id: client id to send the message to (see `BaseServer.on_new_client`)
        """
        self.clients[client_id]["writer"].write(message.encode("utf8"))
        await self.clients[client_id]["writer"].drain()

Send a message to a client.

Parameters

Name Type Description Default
message str message to send required
client_id str client id to send the message to (see BaseServer.on_new_client) required