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
