Case Study
Asynchronous TCP Socket Server
Zero-dependency Python asyncio chat gateway — raw TCP sockets, persistent client state, and real-time broadcast without threading overhead.
- Python
- asyncio
- StreamReader
- StreamWriter
- python
- asyncio
- tcp
- networking
- sockets
- zero-dependency
Overview
Most modern web developers build on top of massive abstraction layers, relying entirely on REST APIs or HTTP frameworks like Express and FastAPI. Building a raw TCP server strips away those abstractions, proving you understand network protocols at the foundational socket level.
This implementation uses Python’s asyncio library to build a non-blocking, multi-user chat server. It mimics the underlying architecture of a classic text-based Multi-User Dungeon (MUD) by keeping a persistent state of connected users and broadcasting messages in real time over an event loop — all without threading overhead.
Part of the DIY build series: Projects 1 · 2 · 3 · 4 · 5 · 6 · 7.
What it implements
- Raw TCP server —
asyncio.start_serveron0.0.0.0:8888, no HTTP layer - Persistent client state — in-memory
clientsdict mapping writers to usernames - Async broadcast —
await writer.drain()yields to the event loop during I/O waits - Connection lifecycle — handshake, read loop, graceful cleanup on disconnect
- Multi-client concurrency — one coroutine per connection, no
threadingmodule
Project setup
mkdir async-tcp-gateway && cd async-tcp-gateway
touch tcp_server.py
The code (tcp_server.py)
State lives purely in memory. asyncio pauses and resumes execution via await whenever network I/O is waiting:
#!/usr/bin/env python3
import asyncio
# --- ANSI Terminal Colors ---
CLR_RESET = "\033[0m"
CLR_CYAN = "\033[96m"
CLR_GREEN = "\033[92m"
CLR_YELLOW = "\033[93m"
CLR_GRAY = "\033[90m"
CLR_MAGENTA = "\033[95m"
class AsyncChatGateway:
def __init__(self):
self.clients = {}
async def broadcast(self, message: str, sender_writer=None):
"""Pushes a message to all connected TCP clients asynchronously."""
for writer in list(self.clients.keys()):
if writer != sender_writer:
try:
writer.write(message.encode('utf-8'))
await writer.drain()
except ConnectionError:
self._remove_client(writer)
def _remove_client(self, writer):
"""Cleans up memory when a connection dies."""
if writer in self.clients:
del self.clients[writer]
async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
"""The coroutine spawned for every new network connection."""
addr = writer.get_extra_info('peername')
welcome_msg = f"{CLR_CYAN}Welcome to the davidcole.cloud Gateway.{CLR_RESET}\n"
welcome_msg += "Enter your handle, traveler: "
writer.write(welcome_msg.encode('utf-8'))
await writer.drain()
name_line = await reader.readline()
if not name_line:
writer.close()
return
username = name_line.decode('utf-8').strip() or "Guest"
self.clients[writer] = username
print(f"[{addr[0]}:{addr[1]}] User '{username}' connected.")
await self.broadcast(f"{CLR_YELLOW}*** {username} has entered the realm ***{CLR_RESET}\n", writer)
try:
while True:
data = await reader.readline()
if not data:
break
message = data.decode('utf-8').strip()
if message:
formatted_msg = f"{CLR_GREEN}{username}{CLR_RESET}: {message}\n"
await self.broadcast(formatted_msg, writer)
except asyncio.CancelledError:
pass
except ConnectionResetError:
pass
finally:
self._remove_client(writer)
print(f"[{addr[0]}:{addr[1]}] User '{username}' disconnected.")
await self.broadcast(f"{CLR_GRAY}*** {username} has vanished into the aether ***{CLR_RESET}\n")
writer.close()
await writer.wait_closed()
async def main():
HOST, PORT = '0.0.0.0', 8888
gateway_state = AsyncChatGateway()
server = await asyncio.start_server(gateway_state.handle_client, HOST, PORT)
print(f"{CLR_MAGENTA}🚀 Async TCP Gateway listening on {HOST}:{PORT}{CLR_RESET}")
print("Awaiting concurrent telnet connections...")
async with server:
await server.serve_forever()
if __name__ == '__main__':
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nGateway shutting down. Connections severed.")
Execution & testing
You need the server plus multiple clients simultaneously.
1. Launch the server
chmod +x tcp_server.py
./tcp_server.py
2. Connect client one
In a second terminal:
telnet 127.0.0.1 8888
Or use nc if telnet is not installed:
nc 127.0.0.1 8888
3. Connect client two
Run the same command in a third terminal.
Type messages in terminal #2 and watch them appear instantly in terminal #3. The server terminal logs IP addresses and routing activity.
Why this shines on a portfolio
- Mastery of the event loop —
await reader.readline()means the server does not freeze waiting for one user; it switches to serve other connected clients immediately. - State management — unlike stateless HTTP, TCP sockets require persistent memory (
self.clients). Clean connection teardown without leaks is a core backend skill.
Related work
- Project 1: Markdown API Router — HTTP layer built on top of the same networking fundamentals
- Project 6: Local Context Server — stdlib Python server pattern, HTTP instead of raw TCP
- Project 7: Multi-Tenant Migration Engine — concurrent Python orchestration with different I/O model