@@ -35,6 +35,19 @@ asyncio calls your callback with two arguments: a
3535:class: `~asyncio.StreamWriter ` for sending data back. Each connection runs
3636as its own coroutine, so multiple clients are handled concurrently.
3737
38+ The :meth: `~asyncio.StreamWriter.write ` method buffers data without sending
39+ it immediately. Awaiting :meth: `~asyncio.StreamWriter.drain ` flushes the
40+ buffer and applies back-pressure if the client is slow to read. Similarly,
41+ :meth: `~asyncio.StreamWriter.close ` initiates shutdown, and awaiting
42+ :meth: `~asyncio.StreamWriter.wait_closed ` waits until the connection is
43+ fully closed.
44+
45+ Using the server as an async context manager (``async with server ``) ensures
46+ it is properly cleaned up when done. Calling
47+ :meth: `~asyncio.Server.serve_forever ` keeps the server running until the
48+ program is interrupted. Finally, :func: `asyncio.run ` starts the event loop
49+ and runs the top-level coroutine.
50+
3851Here is a complete echo server::
3952
4053 import asyncio
@@ -65,13 +78,6 @@ Here is a complete echo server::
6578
6679 asyncio.run(main())
6780
68- The :meth: `~asyncio.StreamWriter.write ` method buffers data without sending
69- it immediately. Awaiting :meth: `~asyncio.StreamWriter.drain ` flushes the
70- buffer and applies back-pressure if the client is slow to read. Similarly,
71- :meth: `~asyncio.StreamWriter.close ` initiates shutdown, and awaiting
72- :meth: `~asyncio.StreamWriter.wait_closed ` waits until the connection is
73- fully closed.
74-
7581To test, run the server in one terminal and connect from another using ``nc ``
7682(or ``telnet ``):
7783
@@ -88,13 +94,40 @@ Building the chat server
8894The chat server extends the echo server with two additions: tracking connected
8995clients and broadcasting messages to everyone.
9096
91- We store each client's name and :class: `~asyncio.StreamWriter ` in a dictionary.
92- When a message arrives, we broadcast it to all other connected clients.
93- :class: `asyncio.TaskGroup ` sends to all recipients concurrently, and
94- :func: `contextlib.suppress ` silently handles any :exc: `ConnectionError ` from
95- clients that have already disconnected.
97+ Client tracking
98+ ---------------
99+
100+ We store each connected client's name and :class: `~asyncio.StreamWriter ` in a
101+ module-level dictionary. When a client connects, ``handle_client `` prompts for
102+ a name and adds the writer to the dictionary. A ``finally `` block ensures the
103+ client is always removed on disconnect, even if the connection drops
104+ unexpectedly.
105+
106+ Broadcasting messages
107+ ---------------------
108+
109+ To send a message to all clients, we define a ``broadcast `` function.
110+ :class: `asyncio.TaskGroup ` sends to all recipients concurrently rather than
111+ one at a time. :func: `contextlib.suppress ` silently handles any
112+ :exc: `ConnectionError ` from clients that have already disconnected::
113+
114+ async def broadcast(message, *, sender=None):
115+ """Send a message to all connected clients except the sender."""
116+ async def send(writer):
117+ with contextlib.suppress(ConnectionError):
118+ writer.write(message.encode())
119+ await writer.drain()
120+
121+ async with asyncio.TaskGroup() as tg:
122+ # Iterate over a copy: clients may leave during the broadcast.
123+ for name, writer in list(connected_clients.items()):
124+ if name != sender:
125+ tg.create_task(send(writer))
126+
127+ The complete chat server
128+ ------------------------
96129
97- ::
130+ Putting it all together ::
98131
99132 import asyncio
100133 import contextlib
0 commit comments