liveBook · Manning

Echo server

As explained in Python Concurrency with asyncio

An echo server is a network server that sends received client data back to the client that transmitted it.

An echo server receives data from a client and sends the same data back. It provides a small, practical way to understand socket communication, blocking behavior, and concurrent network handling. section 3.0 section 3.2.1

The implementation develops through three increasingly capable designs:

  • A blocking socket server reads one client’s data and writes it back, but waiting for that client prevents the single thread from serving others properly. section 3.2.1
  • A non-blocking server registers sockets with selectors, waits for read events, and echoes data only when a socket is ready, supporting multiple clients with low CPU use. section 3.4
  • An asyncio server awaits socket operations in coroutines and creates one task per connection, allowing clients to send and receive concurrently on one thread. section 3.5 section 3.5.2

Asyncio echo flow

The listener awaits a client connection, makes the connection non-blocking, and starts an echo task. That task repeatedly awaits received data and sends the same bytes back until the client stops sending. section 3.5.2

Listing 3.8 Building an asyncio echo server
import asyncio
import socket
from asyncio import AbstractEventLoop
async def echo(connection: socket,
               loop: AbstractEventLoop) -> None:
    while data := await loop.sock_recv(connection, 1024):                #1
        await loop.sock_sendall(connection, data)                        #2
 
 
async def listen_for_connection(server_socket: socket,
                                loop: AbstractEventLoop):
    while True:
        connection, address = await loop.sock_accept(server_socket)
        connection.setblocking(False)
        print(f"Got a connection from {address}")
        asyncio.create_task(echo(connection, loop))                      #3
 
 
async def main():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
    server_address = ('127.0.0.1', 8000)
    server_socket.setblocking(False)
    server_socket.bind(server_address)
    server_socket.listen()
 
    await listen_for_connection(server_socket, asyncio.get_event_loop()) #4
 
asyncio.run(main())

Provides a complete asyncio implementation showing socket acceptance, per-connection tasks, asynchronous reads, and asynchronous writes. section 3.5.2

How Python Concurrency with asyncio covers it

How deep it goes
Progresses from blocking sockets through selectors to asyncio tasks, including event-loop machinery, errors, and shutdown behavior.
Examples
Uses Telnet messages, socket listings, a selector event loop, CPU utilization, and an asyncio listener spawning one task per connection.
What it assumes
Assumes introductory coroutines, tasks, and event-loop knowledge while teaching socket communication, blocking behavior, and selectors progressively.
What you can do after
Build it from scratch with sockets, selectors, and asyncio APIs while handling multiple clients, task errors, and custom shutdown.
How it connects
Connects sockets with blocking and non-blocking I/O, selectors, event loops, coroutines, tasks, concurrency, and graceful shutdown.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Python Concurrency with asyncio. The section links point to the parts of the book it draws on.