Sling Academy
Home/Python/Python Steam: How to start a socket server with asyncio.start_server()

Python Steam: How to start a socket server with asyncio.start_server()

Last updated: February 12, 2024

Overview

Asynchronous programming in Python has seen a significant rise in popularity and usage, thanks to the simplicity and efficiency it brings to the table, especially when dealing with I/O-bound tasks. One common use case in asynchronous programming is creating a socket server that can handle multiple client connections concurrently without blocking. This tutorial aims to guide you through the process of starting an asynchronous socket server in Python using asyncio.start_server().

Understanding asyncio.start_server()

The asyncio module provides the infrastructure for writing single-threaded concurrent code using coroutines, multiplexing I/O access over sockets and other resources, running network clients and servers, and other related primitives. asyncio.start_server() is a high-level function for creating a socket server that kindly handles client connections.

Before diving into the code, make sure you have Python 3.7 or higher installed on your system, as asyncio has seen significant changes and improvements in recent versions.

Basic AsyncIO Socket Server Example

To begin, let’s create a simple server that accepts connections and prints messages received from clients. This example lays the foundation for more complex server functionalities such as broadcasting to multiple clients or handling various client requests simultaneously.

import asyncio

async def handle_client(reader, writer):
    data = await reader.read(100)
    message = data.decode('utf-8')
    print(f"Received: {message}")
    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())

In this basic setup, handle_client is a coroutine that reads data from the client, decodes it, and prints it out. The main coroutine creates the server and binds it to '127.0.0.1' on port 8888. The server is then set to run indefinitely.

Expanding the Server Functionality

With the foundational knowledge of setting up a basic AsyncIO socket server, let’s extend it to handle more complex scenarios like broadcasting messages to multiple clients. Below is an example that keeps track of connected clients and broadcasts any received messages to all connected clients.

import asyncio

clients = {}

async def handle_client(reader, writer):
    info = writer.get_extra_info('peername')
    welcome = f'Connected to server: {info}\n'
    clients[writer] = info

    try:
        while True:
            data = await reader.read(100)
            message = data.decode('utf-8')
            broadcast = f"[Client {info}] {message}"
            for client_writer in clients.keys():
                client_writer.write(broadcast.encode())
                await client_writer.drain()
            if message.lower() == 'quit':
                break
    except asyncio.CancelledError:
        pass
    finally:
        del clients[writer]
        writer.close()

async def main():
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8888)
    print(f"Server started successfully")

    async with server:
        await server.serve_forever()

asyncio.run(main())

This modified server now functions as a simple chat server. The handle_client coroutine has been extended to broadcast messages received from one client to all others. Notably, the process for adding and removing clients from the clients dictionary to keep track of current connections.

Handling Errors and Server Shutdown

When building server applications, it’s crucial to handle errors and provide a clean shutdown process. Here’s how you can add error handling and a graceful shutdown mechanism to your AsyncIO socket server.

import asyncio
import signal

shutdown_event = asyncio.Event()

async def shutdown(signal, loop):
    print(f"Received exit signal {signal.name}...")
    loop.stop()
    shutdown_event.set()

async def main():
    # Server setup goes here
    loop = asyncio.get_running_loop()
    for s in (signal.SIGTERM, signal.SIGINT):
        loop.add_signal_handler(s, lambda s=s: asyncio.create_task(shutdown(s, loop)))

    # Wait for the shutdown event
    await shutdown_event.wait()

    # Close the server
    server.close()
    await server.wait_closed()

    # Perform cleanup operations if necessary

asyncio.run(main())

This approach allows for a more robust server that can handle system signals for termination (like CTRL+C) gracefully, setting the stage for any necessary cleanup before exit.

Conclusion

In this tutorial, we have walked through the basics of creating an asynchronous socket server using asyncio.start_server(). Starting from a simple echo server, we expanded its functionality to a multi-client chat server, and finally, we introduced error handling and graceful shutdown. Through these exercises, you should now have a solid understanding of Python’s asynchronous programming capabilities and how to apply them to real-world networking tasks.

Remember, the examples provided here are foundational and can be expanded further to build more complex server architectures depending on your project’s needs. The asynchronous nature of these servers makes them highly scalable and efficient for handling numerous client connections, making asyncio an excellent choice for network programming in Python.

Next Article: Python asyncio: Adding schedule callbacks to a Future

Previous Article: Python Stream: asyncio.open_connection() function explained (with examples)

Series: Python Asynchronous Programming Tutorials

Python

You May Also Like

  • Introduction to yfinance: Fetching Historical Stock Data in Python
  • Monitoring Volatility and Daily Averages Using cryptocompare
  • Advanced DOM Interactions: XPath and CSS Selectors in Playwright (Python)
  • Automating Strategy Updates and Version Control in freqtrade
  • Setting Up a freqtrade Dashboard for Real-Time Monitoring
  • Deploying freqtrade on a Cloud Server or Docker Environment
  • Optimizing Strategy Parameters with freqtrade’s Hyperopt
  • Risk Management: Setting Stop Loss, Trailing Stops, and ROI in freqtrade
  • Integrating freqtrade with TA-Lib and pandas-ta Indicators
  • Handling Multiple Pairs and Portfolios with freqtrade
  • Using freqtrade’s Backtesting and Hyperopt Modules
  • Developing Custom Trading Strategies for freqtrade
  • Debugging Common freqtrade Errors: Exchange Connectivity and More
  • Configuring freqtrade Bot Settings and Strategy Parameters
  • Installing freqtrade for Automated Crypto Trading in Python
  • Scaling cryptofeed for High-Frequency Trading Environments
  • Building a Real-Time Market Dashboard Using cryptofeed in Python
  • Customizing cryptofeed Callbacks for Advanced Market Insights
  • Integrating cryptofeed into Automated Trading Bots