serve_forever runs an asynchronous server continuously, accepting and serving client connections until the server is terminated. A server returned by start_server can be used with async with so it shuts down properly when execution exits the context. section 8.5
Use serve_forever as the long-running part of an asyncio server:
- Pass a callback or coroutine to start_server; it receives a StreamReader and StreamWriter whenever a client connects. section 8.5
- Await server.serve_forever() inside async with server to keep the service running and close it cleanly afterward. section 8.5 section 8.6
Chat server entry point
async def start_chat_server(self, host, port): server = await asyncio.start_server(self.client_connected, host, port) async with server: await server.serve_forever() The callback can authenticate clients, create message-listening tasks, and remove idle or disconnected users. section 8.6
Listing 8.12 Creating an echo server with server objects
import asyncio
import logging
from asyncio import StreamReader, StreamWriter
class ServerState:
def __init__(self):
self._writers = []
async def add_client(self, reader: StreamReader, writer: StreamWriter): #1
self._writers.append(writer)
await self._on_connect(writer)
asyncio.create_task(self._echo(reader, writer))
async def _on_connect(self, writer: StreamWriter): #2
writer.write(f'Welcome! {len(self._writers)} user(s) are online!\n'.encode())
await writer.drain()
await self._notify_all('New user connected!\n')
async def _echo(self, reader: StreamReader, writer: StreamWriter): #3
try:
while (data := await reader.readline()) != b'':
writer.write(data)
await writer.drain()
self._writers.remove(writer)
await self._notify_all(f'Client disconnected. {len(self._writers)} user(s) are online!\n')
except Exception as e:
logging.exception('Error reading from client.', exc_info=e)
self._writers.remove(writer)
async def _notify_all(self, message: str): #4
for writer in self._writers:
try:
writer.write(message.encode())
await writer.drain()
except ConnectionError as e:
logging.exception('Could not write to client.', exc_info=e)
self._writers.remove(writer)
async def main():
server_state = ServerState()
async def client_connected(reader: StreamReader, writer: #5
StreamWriter) -> None:
await server_state.add_client(reader, writer)
server = await asyncio.start_server(client_connected, '127.0.0.1', 8000) #6
async with server:
await server.serve_forever()
asyncio.run(main())
Inspect a complete echo-server pattern that combines serve_forever with connection state, client callbacks, and StreamWriter management. section 8.5
