Introduction
WebSocket is a computer communications protocol that provides full-duplex communication channels over a single TCP connection. Unlike HTTP’s request-response model, WebSocket enables persistent bidirectional communication between client and server, making it ideal for real-time applications.
This comprehensive guide covers WebSocket protocol mechanics, handshake process, frame format, security considerations, and implementation patterns.
What is WebSocket?
WebSocket was standardized by the IETF as RFC 6455 in 2011, providing a standardized way for web browsers and servers to communicate over a single, long-lived TCP connection.
Key Characteristics
Full Duplex: Bidirectional communication simultaneously.
Persistent Connection: Single handshake, persistent connection.
Low Overhead: No HTTP headers after establishment.
Cross-Origin: Works across origins with proper headers.
Use Cases
- Real-time chat applications
- Live streaming dashboards
- Online gaming
- Financial trading platforms
- Collaborative editing tools
- IoT device communication
Protocol Mechanics
Handshake
The WebSocket handshake begins as an HTTP request and upgrades to WebSocket protocol.
HTTP Request:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com
HTTP Response:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
JavaScript Implementation
// Client-side WebSocket
const ws = new WebSocket('wss://server.example.com/chat');
ws.onopen = () => {
console.log('WebSocket connected');
ws.send(JSON.stringify({ type: 'join', room: 'lobby' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = (event) => {
console.log('Disconnected:', event.code, event.reason);
};
// Send message
ws.send(JSON.stringify({ message: 'Hello' }));
// Close connection
ws.close(1000, 'Closing normally');
Frame Format
WebSocket frames have a compact binary format:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len| Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| | Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+---------------------------------------------------------------+
Frame Types
| Opcode | Meaning |
|---|---|
| 0x0 | Continuation frame |
| 0x1 | Text frame |
| 0x2 | Binary frame |
| 0x8 | Connection close |
| 0x9 | Ping |
| 0xA | Pong |
Python Server
import asyncio
import websockets
async def chat_handler(websocket, path):
# Handle client connection
try:
async for message in websocket:
# Parse incoming message
data = json.loads(message)
if data['type'] == 'message':
# Broadcast to all clients
await broadcast(message)
elif data['type'] == 'ping':
await websocket.send(json.dumps({'type': 'pong'}))
except websockets.exceptions.ConnectionClosed:
print('Client disconnected')
async def main():
async with websockets.serve(chat_handler, "localhost", 8765):
await asyncio.Future() # Run forever
asyncio.run(main())
WebSocket Secure (WSS)
// Always use WSS in production
const ws = new WebSocket('wss://secure.example.com/ws');
// With authentication
const ws = new WebSocket('wss://api.example.com/ws', {
headers: {
'Authorization': `Bearer ${token}`
}
});
Subprotocols
Using Subprotocols
// Client requests subprotocol
const ws = new WebSocket('wss://server.example.com', ['soap', 'wamp']);
// Server selects one
// Sec-WebSocket-Protocol: wamp
// Then use as WAMP client
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg[0] === 16) { // HELLO
// WAMP session established
}
};
Common Subprotocols
- WAMP (WebSocket Application Messaging Protocol)
- SOAP over WebSocket
- MQTT over WebSocket
Scalability
Pub/Sub with Redis
import asyncio
import aioredis
import websockets
import json
class ChatServer:
def __init__(self):
self.redis = None
self.subscriptions = {}
async def connect(self, websocket, path):
client_id = id(websocket)
async for message in websocket:
data = json.loads(message)
if data['type'] == 'subscribe':
channel = data['channel']
await self.redis.subscribe(channel)
self.subscriptions[client_id] = channel
elif data['type'] == 'publish':
channel = data['channel']
message = data['message']
await self.redis.publish(channel, json.dumps(message))
async def handle_redis_message(self, channel, message):
# Broadcast to WebSocket clients
for ws in self.clients:
await ws.send(message)
Load Balancing
# nginx WebSocket proxy
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl http2;
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
}
Best Practices
- Always use WSS in production
- Implement heartbeat/ping-pong
- Handle reconnection gracefully
- Validate all incoming data
- Set appropriate timeouts
- Monitor connection states
Conclusion
WebSocket enables powerful real-time applications with persistent bidirectional communication. Its widespread browser support and simple API make it the go-to choice for building interactive web applications.
Comments