Real-time communication is essential for modern applications - chat, notifications, live updates, and collaborative features. Two primary technologies enable this: WebSockets and Server-Sent Events (SSE).
In this guide, we’ll explore both technologies, their differences, and when to use each.
Understanding Real-Time Communication
The Problem
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Traditional Request-Response โ
โ โ
โ Client โโโโโโโโโโโโโโโโโโโโโโโโโโโโบ Server โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ (Response) โ
โ โ
โ Problem: Server can't push data to client! โ
โ Solution: Use WebSockets or SSE โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Technology Comparison
real_time_options = {
"polling": {
"description": "Client repeatedly asks for updates",
"pros": ["Simple", "Works everywhere"],
"cons": ["Wastes bandwidth", "Delayed updates"],
"use_when": "Simple use cases only"
},
"long_polling": {
"description": "Server holds request until data available",
"pros": ["Near real-time", "More efficient"],
"cons": ["Complex", "Still not true push"],
"use_when": "Temporary solution"
},
"webhooks": {
"description": "Server calls client URL",
"pros": ["True push", "Simple"],
"cons": ["Requires public URL", "Complex"],
"use_when": "Machine-to-machine"
},
"sse": {
"description": "Server pushes to client over HTTP",
"pros": ["True push", "Simple", "HTTP/2"],
"cons": ["One-way only", "Browser limits"],
"use_when": "Server to client only"
},
"websockets": {
"description": "Full-duplex communication",
"pros": ["Bi-directional", "Low latency", "Binary"],
"cons": ["Requires upgrade", "More complex"],
"use_when": "Chat, games, bi-directional"
}
}
WebSockets
How WebSockets Work
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ WebSocket Handshake โ
โ โ
โ Client โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ GET /ws HTTP/1.1 โ
โ Host: example.com โ
โ Upgrade: websocket โ
โ Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== โ
โ โ
โ Server โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ HTTP/1.1 101 Switching Protocols โ
โ Upgrade: websocket โ
โ Sec-WebSocket-Accept: s3pPL... โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ NOW: Full-duplex TCP connection! โ
โ โ
โ Client โโโโโโโโโโโโโโโโโโโโโบ Server โ
โ (messages flow freely in both directions) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
WebSocket Protocol
# WebSocket frame format
frame_structure = {
"FIN": "1 bit - Last frame in message",
"RSV1-3": "3 bits - Reserved for extensions",
"OPCODE": "4 bits - Message type",
"MASK": "1 bit - ClientโServer is masked",
"PAYLOAD_LENGTH": "7 bits (+ extended)",
"MASKING_KEY": "0/4 bytes",
"PAYLOAD_DATA": "Data"
}
opcodes = {
"0x0": "Continuation frame",
"0x1": "Text frame",
"0x2": "Binary frame",
"0x8": "Connection close",
"0x9": "Ping",
"0xA": "Pong"
}
Python WebSocket Server
# Using websockets library
import asyncio
import websockets
import json
from datetime import datetime
# Store connected clients
connected_clients = set()
async def chat_handler(websocket):
"""Handle WebSocket connections"""
client_id = id(websocket)
connected_clients.add(websocket)
try:
# Send welcome message
await websocket.send(json.dumps({
"type": "welcome",
"message": "Connected to chat",
"client_id": client_id
}))
# Handle incoming messages
async for message in websocket:
data = json.loads(message)
if data["type"] == "chat":
# Broadcast to all clients
broadcast = {
"type": "message",
"client_id": client_id,
"message": data["message"],
"timestamp": datetime.utcnow().isoformat()
}
# Send to all connected clients
await asyncio.gather(
*[client.send(json.dumps(broadcast))
for client in connected_clients]
)
elif data["type"] == "ping":
await websocket.send(json.dumps({"type": "pong"}))
except websockets.exceptions.ConnectionClosed:
pass
finally:
connected_clients.remove(websocket)
# Run server
async def main():
async with websockets.serve(chat_handler, "localhost", 8765):
await asyncio.Future() # Run forever
asyncio.run(main())
WebSocket Client
// Browser WebSocket client
const ws = new WebSocket('wss://example.com/ws');
// Connection opened
ws.onopen = () => {
console.log('Connected to WebSocket');
ws.send(JSON.stringify({ type: 'hello' }));
};
// Handle messages
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'message') {
addMessage(data.message);
}
};
// Handle errors
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
// Handle disconnection
ws.onclose = () => {
console.log('Disconnected');
// Reconnect after 5 seconds
setTimeout(connect, 5000);
};
// Send message
function sendMessage(text) {
ws.send(JSON.stringify({
type: 'chat',
message: text
}));
}
Server-Sent Events (SSE)
How SSE Works
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Server-Sent Events โ
โ โ
โ Client โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ GET /events HTTP/1.1 โ
โ Accept: text/event-stream โ
โ โ
โ Server โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ HTTP/1.1 200 OK โ
โ Content-Type: text/event-stream โ
โ โ
โ data: {"type": "update", "value": 1} โ
โ data: {"type": "update", "value": 2} โ
โ data: {"type": "update", "value": 3} โ
โ ... โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ One-way: Server pushes to client โ
โ Uses standard HTTP (no special protocol) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SSE Format
# SSE message format
message_parts:
- "data: The message payload"
- "id: Optional event ID (for reconnection)"
- "event: Optional event type"
- "retry: Optional retry interval (ms)"
# Example messages
examples:
simple_data:
content: |
data: Hello
json_data:
content: |
data: {"message": "Hello", "count": 42}
with_id:
content: |
id: 123
data: Update
with_retry:
content: |
retry: 5000
data: Auto-reconnect in 5 seconds
Python SSE Server
# Using Flask for SSE
from flask import Flask, Response, stream_with_context
import json
import time
app = Flask(__name__)
def generate_events():
"""Generate SSE events"""
count = 0
while True:
# Create event data
data = json.dumps({
"count": count,
"timestamp": time.time()
})
# Yield SSE-formatted message
yield f"data: {data}\n\n"
count += 1
time.sleep(1)
@app.route('/events')
def events():
"""SSE endpoint"""
return Response(
stream_with_context(generate_events()),
mimetype='text/event-stream'
)
# With event types
def generate_notifications():
"""Generate typed events"""
for notification in get_notifications():
yield f"event: notification\ndata: {json.dumps(notification)}\n\n"
@app.route('/notifications')
def notifications():
return Response(
stream_with_context(generate_notifications()),
mimetype='text/event-stream'
)
SSE Client
// Browser SSE client
const eventSource = new EventSource('/events');
// Handle default messages
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Message:', data);
};
// Handle custom event types
eventSource.addEventListener('notification', (event) => {
const notification = JSON.parse(event.data);
showNotification(notification);
});
// Connection opened
eventSource.onopen = () => {
console.log('Connected to SSE');
};
// Handle errors
eventSource.onerror = (error) => {
console.error('SSE error:', error);
// EventSource automatically reconnects!
};
// Close connection when done
function disconnect() {
eventSource.close();
}
Choosing Between WebSockets and SSE
Decision Matrix
| Factor | WebSockets | SSE |
|---|---|---|
| Direction | Bi-directional | ServerโClient |
| Protocol | WebSocket (upgrade) | HTTP |
| Browser Support | All modern | All modern |
| Connections | Single TCP | New HTTP each time |
| Binary Data | Yes | No (text only) |
| Firewalls | Sometimes blocked | Usually fine |
| Complexity | Higher | Lower |
| Auto-reconnect | Manual | Built-in |
When to Use WebSockets
websocket_use_cases:
- "Chat applications"
- "Multiplayer games"
- "Collaborative editing"
- "Financial tickers"
- "Real-time dashboards with user input"
- "Bi-directional communication needed"
When to Use SSE
sse_use_cases:
- "Live feeds (Twitter, news)"
- "Notifications"
- "Progress updates"
- "Status monitoring"
- "Server push only"
- "Behind restrictive firewalls"
- "Simpler implementation needed"
Scaling Considerations
WebSocket Scaling
# WebSocket scaling strategies
scaling_strategies = {
"sticky_sessions": {
"description": "Route client to same server",
"implementation": "Load balancer with session affinity"
},
"redis_pubsub": {
"description": "Share messages across servers",
"implementation": "Redis pub/sub for message distribution"
},
"message_queue": {
"description": "Use queue for async processing",
"implementation": "RabbitMQ or Kafka"
}
}
# Example: Redis pub/sub with Socket.io
const io = require('socket.io')(server);
io.on('connection', (socket) => {
socket.on('message', (msg) => {
// Publish to Redis
redisClient.publish('messages', JSON.stringify(msg));
});
});
// Multiple servers subscribe to Redis
redisClient.subscribe('messages', (err) => {
redisClient.on('message', (channel, message) => {
io.emit('message', JSON.parse(message));
});
});
SSE Scaling
# SSE scaling considerations
scaling:
browser_limits:
- "Max 6 connections per domain (HTTP/1.1)"
- "No limit with HTTP/2"
server_limits:
- "Keep-alive connections consume memory"
- "Set appropriate timeouts"
solutions:
- "Use HTTP/2 for more connections"
- "Implement connection pooling"
- "Use CDN for large scale"
Security
WebSocket Security
# WebSocket security
security_considerations = {
"authentication": {
"recommendation": "Authenticate on connection, validate per message",
"example": "JWT in connection query or first message"
},
"authorization": {
"recommendation": "Check permissions for each message type"
},
"input_validation": {
"recommendation": "Validate all incoming data"
},
"rate_limiting": {
"recommendation": "Prevent message flooding"
},
"wss": {
"recommendation": "Always use WSS (WebSocket Secure)"
}
}
# Secure WebSocket with authentication
async def secure_handler(websocket, path):
# Get token from query string
token = parse_qs(path).get('token', [None])[0]
# Validate token
user = await validate_token(token)
if not user:
await websocket.close()
return
# Continue with authenticated session
await chat_handler(websocket, user)
SSE Security
# SSE security
security:
- "Use HTTPS"
- "Implement authentication (cookies/tokens)"
- "Validate origin header"
- "Set appropriate CORS headers"
- "Implement rate limiting"
Conclusion
Choose based on your needs:
- WebSockets: Full bi-directional communication, chat, games
- SSE: ServerโClient push, simpler, better HTTP compatibility
Both are essential tools in your real-time communication toolkit.
Comments