SSE vs WebSocket — Complete Guide
In this tutorial, you will learn about SSE vs Websocket. We cover key concepts, practical examples, and best practices to help you master this topic.
Compare Server-Sent Events vs WebSockets: one-way push vs bidirectional, HTTP simplicity vs handshake protocol, auto-reconnect vs manual, and choosing the right real-time technology.
What You Learn
You will learn the key differences between SSE and WebSockets, when to use each, performance characteristics, browser support, implementation complexity, and how to choose the right technology for your use case.
Why It Matters
Choosing the wrong real-time technology leads to unnecessary complexity or missing features. SSE is simpler for server-to-client streaming. WebSockets are required for bidirectional communication. Understanding the trade-offs saves development time and improves user experience.
Real-World Use
DodaTech uses SSE for deployment logs and monitoring dashboards (server pushes to client). They use WebSockets for the live collaboration feature in their code editor (bidirectional editing). Each technology fits its use case perfectly.
Direction of Communication
# SSE: Server -> Client only
# The server pushes data to the client over a single HTTP connection.
# To send data to the server, the client must make separate HTTP requests.
# WebSocket: Bidirectional
# Both server and client can send messages at any time.
# A single connection handles both directions.
import asyncio
import json
# SSE pattern (one-way)
async def sse_push(writer, data):
writer.write(f"data: {json.dumps(data)}\n\n".encode())
await writer.drain()
# WebSocket pattern (bidirectional)
async def ws_handler(websocket):
async for message in websocket:
# Receive from client
data = json.loads(message)
# Send to client
await websocket.send(json.dumps({'response': 'ok'}))
Connection Establishment
# SSE: Standard HTTP GET request
# Client: new EventSource('/stream')
# Server: Content-Type: text/event-stream
# WebSocket: HTTP upgrade handshake
# Client: new WebSocket('ws://host/stream')
# Server: 101 Switching Protocols
# Then: bidirectional frames
import asyncio
from aiohttp import web
# SSE route
async def sse_handler(request):
response = web.StreamResponse()
response.headers['Content-Type'] = 'text/event-stream'
response.headers['Cache-Control'] = 'no-cache'
await response.prepare(request)
return response
# WebSocket route
async def ws_handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
async for msg in ws:
if msg.type == web.MsgType.TEXT:
await ws.send_str(f"Echo: {msg.data}")
return ws
Browser Support
browser_support = {
'chrome': {'sse': 'Yes', 'websocket': 'Yes'},
'firefox': {'sse': 'Yes', 'websocket': 'Yes'},
'safari': {'sse': 'Yes', 'websocket': 'Yes'},
'edge': {'sse': 'Yes', 'websocket': 'Yes'},
'ie': {'sse': 'No (IE 11 via polyfill)', 'websocket': 'Yes (IE 10+)'},
'opera': {'sse': 'Yes', 'websocket': 'Yes'},
'mobile_safari': {'sse': 'Yes', 'websocket': 'Yes'},
'chrome_android': {'sse': 'Yes', 'websocket': 'Yes'},
}
for browser, support in browser_support.items():
print(f"{browser:15s} SSE: {support['sse']:25s} WS: {support['websocket']}")
Expected output:
chrome SSE: Yes WS: Yes
firefox SSE: Yes WS: Yes
safari SSE: Yes WS: Yes
edge SSE: Yes WS: Yes
ie SSE: No (IE 11 via polyfill) WS: Yes (IE 10+)
Auto-Reconnection
// SSE has built-in auto-reconnection
const source = new EventSource('/events');
// Browser automatically reconnects on connection loss
// No code needed for basic reconnection
// WebSocket requires manual reconnection logic
function connectWebSocket() {
const ws = new WebSocket('ws://host/ws');
ws.onopen = () => console.log('Connected');
ws.onclose = () => {
console.log('Disconnected, reconnecting in 3s...');
setTimeout(connectWebSocket, 3000);
};
ws.onerror = (err) => {
console.error('Error:', err);
ws.close();
};
}
connectWebSocket();
Performance Comparison
import time
import sys
def simulate_sse(messages=1000):
overhead_per_message = len("data: {}\n\n")
data = 'x' * 100
total = messages * (overhead_per_message + len(data))
return {
'protocol': 'SSE',
'messages': messages,
'overhead_per_msg': overhead_per_message,
'total_bytes': total,
'http_overhead': 'Initial request headers only',
}
def simulate_websocket(messages=1000):
frame_overhead = 6
data = 'x' * 100
total = messages * (frame_overhead + len(data))
return {
'protocol': 'WebSocket',
'messages': messages,
'overhead_per_msg': frame_overhead,
'total_bytes': total,
'http_overhead': 'Upgrade handshake + frames',
}
sse = simulate_sse()
ws = simulate_websocket()
print(f"SSE: {sse['total_bytes']} bytes ({sse['overhead_per_msg']} bytes/msg overhead)")
print(f"WS: {ws['total_bytes']} bytes ({ws['overhead_per_msg']} bytes/msg overhead)")
When to Choose What
def recommend_technology(needs_bidirectional, needs_binary, needs_http_proxy):
if needs_bidirectional:
return "WebSocket"
if needs_binary:
return "WebSocket (binary frames)"
if needs_http_proxy:
return "SSE (standard HTTP works through proxies)"
return "SSE (simpler, auto-reconnect, HTTP-based)"
scenarios = [
("Live dashboard updates", False, False, True),
("Chat application", True, False, False),
("File transfer", False, True, False),
("Notification feed", False, False, True),
("Collaborative editing", True, False, False),
("Log streaming", False, False, True),
]
for scenario, bidir, binary, proxy in scenarios:
tech = recommend_technology(bidir, binary, proxy)
print(f"{scenario:30s} -> {tech}")
Expected output:
Live dashboard updates -> SSE
Chat application -> WebSocket
File transfer -> WebSocket (binary frames)
Notification feed -> SSE
Collaborative editing -> WebSocket
Log streaming -> SSE
Common Mistakes
1. Using WebSockets for One-Way Push
If you only need server-to-client updates, SSE is simpler. WebSockets add unnecessary complexity, handshake overhead, and manual reconnection code.
2. Using SSE for Bidirectional Communication
SSE is one-way. For bidirectional, pair SSE with HTTP requests or use WebSockets. Trying to force SSE into bidirectional creates awkward workarounds.
3. Ignoring Connection Limits
HTTP/1.1 limits 6 concurrent connections per host. Multiple SSE tabs exhaust this limit quickly. Use HTTP/2 or a shared worker.
4. Not Handling WebSocket Reconnection
WebSocket does not auto-reconnect. Implement reconnection logic with exponential backoff. SSE handles this natively.
5. Expecting SSE to Handle Binary Data
SSE is text-only (UTF-8). For binary data, use WebSockets or encode binary as base64 in SSE.
Practice Questions
1. What is the main difference between SSE and WebSocket?
SSE is one-way server-to-client over standard HTTP. WebSocket is bidirectional with a special handshake and frame protocol.
2. Does SSE support binary data?
No. SSE is text-only (UTF-8). Binary data must be encoded as base64. WebSocket supports binary frames natively.
3. How do browsers handle SSE disconnection?
EventSource automatically reconnects. The browser sends a new HTTP request with Last-Event-ID if available.
4. When should you choose WebSocket over SSE?
For bidirectional communication, binary data, or when the client needs to send messages as frequently as it receives them.
Challenge
Design a real-time system that uses both SSE and WebSocket appropriately: live metrics dashboard (SSE), admin command execution (WebSocket), user notification feed (SSE with HTTP POST for sending), and file upload progress (SSE for progress, HTTP for upload).
FAQ
Mini Project: Protocol Comparison
import asyncio
import time
import json
async def sse_simulation(client_count=10, events_per_client=50):
print(f"SSE: {client_count} clients, {events_per_client} events each")
start = time.time()
# SSE: single HTTP response streaming to all clients
elapsed = time.time() - start
print(f" Connection: HTTP GET (simple)")
print(f" Reconnection: Built-in")
print(f" Server push: {events_per_client} text events")
print(f" Total connections: {client_count}")
async def ws_simulation(client_count=10, messages_per_client=50):
print(f"WebSocket: {client_count} clients, {messages_per_client} messages each")
print(f" Connection: HTTP upgrade handshake")
print(f" Reconnection: Manual")
print(f" Bidirectional: {messages_per_client * 2} messages (send + receive)")
print(f" Total connections: {client_count}")
asyncio.run(sse_simulation())
asyncio.run(ws_simulation())
What's Next
Now that you understand the differences, learn the event stream format in detail, then explore the EventSource API.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro