How to Implement WebSockets in Python
This article provides an overview of how Python facilitates
real-time, bidirectional communication using WebSockets. It explores the
technical foundations of the WebSocket protocol within the Python
ecosystem, compares the lightweight websockets library with
the full-featured Django channels framework, and walks
through implementation examples for building both standalone services
and integrated web applications.
Understanding WebSockets in Python
The WebSocket protocol enables persistent, low-latency, full-duplex communication between a client and a server over a single TCP connection. Unlike traditional HTTP request-response cycles, WebSockets allow either party to send data independently at any time.
In Python, WebSockets are primarily powered by asynchronous I/O
(asyncio) or the Asynchronous Server Gateway Interface
(ASGI) specification, allowing servers to handle thousands of concurrent
open connections without thread-blocking overhead.
Implementation with
the websockets Library
The websockets library is a lightweight,
high-performance package built directly on Python’s native
asyncio module. It is best suited for standalone
microservices, real-time data feeds, and applications that do not
require an extensive web framework.
1. Building a Server
The library provides an asynchronous context manager and event loop integration to manage client handshakes and message streaming:
import asyncio
import websockets
async def echo_handler(websocket):
async for message in websocket:
print(f"Received message: {message}")
await websocket.send(f"Echo: {message}")
async def main():
async with websockets.serve(echo_handler, "localhost", 8765):
print("WebSocket server running on ws://localhost:8765")
await asyncio.Future() # Keeps the server running
if __name__ == "__main__":
asyncio.run(main())2. Building a Client
Connecting to a server follows an identical asynchronous design pattern:
import asyncio
import websockets
async def send_message():
uri = "ws://localhost:8765"
async with websockets.connect(uri) as websocket:
await websocket.send("Hello, Server!")
response = await websocket.recv()
print(f"Server response: {response}")
if __name__ == "__main__":
asyncio.run(send_message())In this model, the event loop handles context switching automatically whenever an operation waits for network I/O, ensuring high throughput with minimal resource consumption.
Implementation with Django Channels
Django channels extends Django to handle asynchronous
protocols like WebSockets alongside standard HTTP. It adopts the ASGI
standard, decoupling protocol decoding from application logic and
integrating seamlessly with Django’s session and authentication
systems.
1. Defining Consumers
A Consumer in Channels is the equivalent of a Django view, but
designed for persistent connections. The
AsyncWebsocketConsumer handles connection lifecycles and
messaging events:
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = "general"
self.room_group_name = f"chat_{self.room_name}"
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def receive(self, text_data):
data = json.loads(text_data)
message = data.get("message", "")
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
"type": "chat_message",
"message": message
}
)
async def chat_message(self, event):
message = event["message"]
# Send message to WebSocket
await self.send(text_data=json.dumps({"message": message}))2. Routing and Channel Layers
Channels routes incoming WebSocket requests through an ASGI routing
tree rather than standard urls.py:
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r"ws/chat/$", consumers.ChatConsumer.as_asgi()),
]To coordinate communication across distributed workers or server instances, Channels uses a Channel Layer (commonly backed by Redis). This acts as a message broker, allowing messages published on one process to broadcast across all active consumer instances listening to a specific group.
Choosing Between
websockets and channels
- Use
websocketswhen developing standalone, dedicated microservices, high-frequency data pipelines, or applications with simple architectures where an entire web framework introduces unnecessary complexity. - Use Django
channelswhen building complex, multi-user web platforms that require user authentication, database persistence, session sharing, and multi-node message broadcasting via Redis.