Python: How to create your own asyncio TCP server (and test it using cURL)

Updated: February 11, 2024 By: Guest Contributor Post a comment

Python offers several optimizations and improvements, especially in the realm of asynchronous programming. In this tutorial, we’ll dive into creating an asyncio TCP server in modern Python, demonstrating the power and simplicity asyncio offers for handling networking tasks. We’ll also explore how to test our server using cURL, a versatile command-line tool for transferring data with URLs.

Understanding asyncio

Before we begin, it’s crucial to understand what asyncio is and why it’s beneficial for network programming. Asyncio is a Python library that provides a framework for writing single-threaded concurrent code using coroutines, multiplexing I/O access over sockets and other resources. It allows for efficient handling of large numbers of connections, making it perfect for creating servers.

Setting Up Your Environment

Start by ensuring you have Python 3.11 or newer installed on your machine. You can check this by running:

python --version

If you don’t have it, download and install it from the official Python website. This tutorial assumes basic familiarity with Python syntax and the command line.

Creating the Echo Server

We’ll start by creating a simple echo server that listens on a specific port and echoes back whatever data is sent to it. This example is perfect for understanding the basics of an asyncio TCP server. Here’s a basic server setup:

import asyncio

async def handle_client(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')
    print(f"Received {message} from {addr}")

    writer.write(data)
    await writer.drain()
    writer.close()

async def main():
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8888)
    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()

asyncio.run(main())

This code snippet defines an echo server with asyncio. The handle_client coroutine reads data from the client, prints it, then sends the same data back. The server listens on localhost port 8888.

Testing with cURL

Once our server is running, we can test it using cURL. cURL is typically used for HTTP requests, but it also supports sending raw data to a TCP server, which is perfect for our test. Here’s how you can do it:

curl -v telnet://localhost:8888

Following this command, you can type your message and press Enter. The server will echo back your input. Note that you might need to press Enter twice because of how TCP is framed.

Expanding The Server

Building on the echo server, you can create more sophisticated asynchronous TCP servers handling complex logic. For instance, you could modify handle_client to parse incoming messages as commands and interact with databases or other network services asynchronously.

Error Handling

It’s essential to handle potential errors in network programming. You can add exception handling in handle_client like so:

async def handle_client(reader, writer):
    try:
        data = await reader.read(100)
        # ... handle data
    except Exception as e:
        print(f'An error occurred: {e}')
    finally:
        writer.close()

This addition helps ensure that the writer is closed properly even if an error occurs, preventing resource leaks.

Conclusion

This tutorial covered creating a simple asyncio TCP server in Python and testing it with cURL. Asynchronous programming can significantly enhance the performance and scalability of network applications, and asyncio with Python makes it more accessible and powerful than ever. Experiment with the provided examples, and consider how you can apply asyncio’s capabilities to your projects.