Skip to main content
โšก Calmops

WebSockets vs Server-Sent Events: Real-Time Communication

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