Asynchronous Server/Client with Python [0x01]: Starting our Server

David Mentgen
TestingOnProd
Published in
7 min readOct 10, 2021

In this project series, we are going to create a group chat using Python. One very important tool we’re going to utilize here is called asyncio, which will allow us to take advantage of asynchronous programming. Asynchronous programming is particularly well suited to projects that need to be able to handle many different tasks simultaneously. In a group chat, we can expect that the server will need to be able to juggle multiple people typing at once, sending read receipts, and pushing out notifications to individual users all at the same time. Before we jump right into the code, let’s take a step back and get an overview of what we’re building.

Link to part 0x02: Logging

Github link and other useful documentation/references can be found below in the “Sauce, References, & Documentation” section!

What is Asynchronous Programming?

First, let’s revisit a concept you may not know that you’re already familiar with; synchronous programming. Synchronous programming is the execution of code in which each task is completed one after the other. If you were making a PB&J, you would get out the bread. Then you would put the peanut butter on one slice. Then you’d put up the peanut butter. Then you would get the jelly out of the fridge. Then you would put jelly on the other slice. Then you would put them together. Most people will learn and become comfortable with synchronous programming first.

In asynchronous programming, one task can be executed, but the computer does not need to wait for that task to finish before moving onto the next one. Perhaps a diagram can help you visualize synchronous vs asynchronous programming:

Asynchronous programming is incredibly useful for server/client code, which is why we are going to use it for this project. There will be a lot of tasks that need to be carried out essentially at the same time, and the only real way to accomplish this is by shifting our attention to new tasks while others are running “behind-the-scenes”. When I think of asynchronous programming, I like to think of it as a lot like cooking a meal. For example, I might turn on the front burner to heat up my pot of tomato sauce, and then immediately redirect my attention to filling a different pot with water to boil the noodles. After the water starts heating up, I can start buttering some bread. Each of these tasks are being initialized, but I don’t have to wait until one task is completed before I start the next since some of them are then simply a matter of waiting. As all of these tasks are happening, I might occasionally check the status of each of them to decide whether or not I need to turn down the heat or end the task and proceed to the next step for that item.

In this case, our server will be juggling a bunch of different tasks in a similar manner. The server we write will be able to continuously listen for any incoming connections, receive messages from clients, send messages to clients, and maybe even handle any server-side commands that an admin might want to execute to manage their server.

Getting Started

For this project, we will only be working on our Server.py file. Let’s go ahead and add the following to the top of our file:

Creating the Server

To get started, we’re going to first create our Server’s constructor, which will take in a host ip, a port, and asyncio’s event loop for handling our tasks:

We are going to have two command-line arguments that we will input: the host ip and the port. We’ll first do a quick and dirty check to ensure that the user input has the correct number of arguments. Once that is passed, we will create our event loop using asyncio, and then we will pass the provided host ip, port, and the created event loop into our Server:

Now, we can quickly test a few things to make sure our program is working as expected:

So far, so good! The screenshot above shows that our program is only proceeding forward if the user provides the correct number of arguments. Additional checks could likely be added to make sure the host ip and port are received in the proper formats, but for the sake of time, we won’t be worrying about those here. The screenshot also shows that with the correct arguments, the server is being constructed, as indicated by our print message!

Now that we have our Server class started, the real fun can begin. Let’s go ahead and define the function that will be used to start our server using our event loop.

Asyncio will take in three arguments:

  • Arg 1: Client Accepted Callback — A function that we will define that determine how our event loop handles new client connections to the server
  • Arg 2: IP — The IP we provided in __init__ above
  • Arg 3: Port — The Port we provided in __init__ above

Asyncio will use these arguments to create our socket server. We will save that server object in self.server.

Moving forward, we then take our server object and start our event loop with lines 10–11.

I went ahead and tossed this section of code within a try/except. In the event of an error, I’ll just print that to the screen for now. I added a catch for KeyboardInterrupt too because I thought it might be useful to know if/when the server crashes because of CTRL+C (which I will probably be using a lot). In the event that any exception occurs, the server will shutdown. For now, this will be the end of our program because the Event Loop will no longer be running.

Going back to our Client Accepted Callback referenced in Arg 1 of asyncio.start_server(), you may have noticed that we do not have self.accept_client() defined. We will now take a moment to code up our client accepting and handling functions.

I’ll go ahead and provide the code for those two together:

You may also notice that handle_client() is not a regular function because it has the “async” keyword. As you might be able to infer, this means that it is an asynchronous function. This function will be capable of executing in the background in the event that we need to “await” any results. In this function, we do exactly just that on line 31, when we wait for client input. Keep in mind that a client might not always have a message for our server to read, so when our code hits this line it will “wait” in the background until results are ready for us to process.

At this point, our server is now capable of accepting and handling multiple clients. To test this locally, I will be connecting to localhost (127.0.0.1) on port 10000. I will use netcat in separate terminals to connect to my server. We can see this in action here:

In our test, we can see that everything is working exactly as we programmed it so far. However, you may notice that the clients are not able to see each other’s messages. In fact, as of right now, only the server is getting any meaningful information. That is because we’ll need to keep adding on to our server for additional features, like broadcasting messages to all of the clients. It might also be useful to modify our code to help us identify who our users are as they chat. These are all features I plan to cover in future posts, but this is a solid start. Stay tuned for the next installment!

Conclusion 0x01

As the title of this post might suggest, this is the first part of a coding series that is still in the works. I sincerely hope that if you’ve made it this far into my post that you’ve enjoyed it and have been able to find the content informative in some way. I always try to push myself to learn something new everyday, so if you notice any errors or code smells above, I’d encourage you to please point them out to me so that I can correct them accordingly.

In part 0x02, I will probably take a brief step back from Server/Client and add a logging system to our server. This code is going to get very large very quickly, so having a good logging system established early on will help us debug any problems that may crop up along way.

Link to Part 0x02: Logging

Sauce, References, & Documentation

Github: https://github.com/D3ISM3/Python-Asynchronous-Server-Client
Python Asyncio Documentation: https://docs.python.org/3/library/asyncio.html

Originally published at https://testingonprod.com on October 10, 2021.

--

--