Asynchronous Server/Client with Python [0x03]: Client Data & Server Broadcast

David Mentgen
TestingOnProd
Published in
5 min readOct 31, 2021

In part 0x03, we will be making some modifications to our server. By the end of this post, our server should be able to store basic information about each client that connects to it and it should be able to broadcast messages back out to each of the clients.

Client Data

Before we start coding, let’s consider what client data we may want to track:

  • StreamReader — Used for reading messages from a specific client
  • StreamWriter — Used for writing messages to a specific client
  • IP — IP associated with a client
  • Port — Port associated with a client
  • Nickname — Nickname that will be used for labeling messages sent/received by a client

We will plan to store all of this information in it’s own class named Client. Alongside these properties, we will also plan to include a function for the client class that will retrieve messages from the StreamReader for the server. We will touch more on the specifics of this later in the code section of this post.

The nickname attribute for our client will expand on our client commands. In this case, we will plan for our client to be able to change it’s nickname via a /nick <nickname> command. We will implement this in the same function that handles our client's quit command.

Broadcasting Messages

Our implementation of broadcasting messages will be simple. In order to implement message broadcasting, our server will need to be modified to keep track of all connected clients. We will plan to keep track of our clients inside the new server property clients.

In our code following this section, you will see that our clients property is just a dictionary of clients that we can iterate over for broadcasting messages.

Code

  • Lines 6–11: We will be initializing our client with the properties mentioned above when we discussed the client data we wanted to track
  • Lines 13–17: In Python, a class can define a __str__ function which defines what is outputted when a class object is directly printed. In this case, we will simply use this to print our client's nickname, ip, and port.
  • Lines 19–45: Define our getter properties for our private variables.
  • Lines 47–61: Our function for retrieving messages from a client. Our server will use this to receive and process messages sent by each of our clients.

For this, I will only be describing the lines of code that are new. If you’re not familiar with another line of code, it’s very likely that it has already been touched on in a previous blog post on this series. Feel free to go back to posts 0x01 or 0x02 for more info!

  • Line 6: Import our new Client class. This line assumes that Client.py exists within the same directory.
  • Line 25: Initialize our new clients property. This generates an empty dictionary that we will use as clients join/disconnect from our server
  • Lines 45–47: Define our new Server property for our private clients variable.
  • Line 97: We added this line to ensure that if/when our Server shuts down, all of our clients are properly disconnected.
  • Line 111: Initialize a new client as soon as it connects to our server with it’s generated StreamWriter and StreamReader.
  • Line 112: Direct our client to operate on our self.handle_client() function which will handle the reading/writing of our client’s messages/commands. Notice that we modified our function so that it accepts a client object instead of the raw StreamWriter and StreamReader objects.
  • Line 113: Add our newly created client to our list so that our Server can keep track of it.
  • Line 119: This is a new capability provided by asyncio that will run this callback when our handle_client task ends. In this case, we want to execute our server’s new self.disconnect_client() function to ensure that our client is properly disconnected.
  • Line 121–148: Our handle_client() function will be used to receive and process any incoming messages from our client. In here, I added the capability for our server to change our client's nickname. I also went ahead and sent messages through our new broadcast_message() function so that all connected clients can receive messages from other clients.
  • Lines 150–161: handle_client_command() will be called if our client ever sends a message preceded by the / character. This will indicate to our server that our client might want to send execute a command. If a valid command is sent to our server, it will be processed accordingly, otherwise the user will be informed that their command is invalid.
  • Lines 163–175: broadcast_message() iterates over our server's clients property and sends a message out to all clients that our server is tracking.
  • Lines 177–194: disconnect_client() will disconnect a client from our server. When a client disconnects, our server will broadcast to all connected clients that someone has disconnected.
  • Lines 196–203: shutdown_server() will send out quit messages to our clients. As of right now, this doesn't actually do anything but we may use this later in the future if we decide to create our own python clients instead of using netcat. At the end of this function, we will then finally shutdown our asyncio loop.

Testing our Chatroom

We can test our python chatroom locally by starting up our server with the same command from the last few sections:
python3 Server.py 127.0.0.1 55556

Conclusion

In this post, we modified our Server.py so that it can track our connected clients and broadcast messages to it's connected clients. Along with these updates, we also updated our server so that it could handle commands from our clients. Hopefully this section provided you with enough room for you to expand on it how you see fit for your own needs.

In the next section of this series, we will focus on expanding our Client.py so that we can stop using netcat.

Link to 0x04: TBD

Sauce, References, & Documentation

Github: https://github.com/D3ISM3/Python-Asynchronous-Server-Client
Part 0x01 Starting Our Server: https://testingonprod.com/2021/10/10/asynchronous-server-client-with-python-0x01-starting-our-server/
Part 0x02 Logging: https://testingonprod.com/2021/10/17/asynchronous-server-client-with-python-0x02-logging/

Originally published at http://testingonprod.com on October 31, 2021.

--

--