Introduction
WebSocket is a communication protocol that provides full-duplex communication channels over a single TCP connection. It enables real-time, bidirectional communication between clients and servers, making it essential for chat applications, live updates, gaming, and other real-time systems.
This guide covers:
- WebSocket Fundamentals: Protocol, handshake, and connection lifecycle
- Connection Management: Establishing, maintaining, and closing connections
- Message Handling: Text and binary messages, framing
- Scaling: Horizontal scaling strategies and challenges
- Best Practices: Error handling, reconnection, and performance
What is WebSocket?
WebSocket is a protocol that:
- Full-Duplex: Bidirectional communication
- Low Latency: Real-time data transfer
- Persistent Connection: Single TCP connection
- Efficient: Minimal overhead after handshake
- Web Standard: Works in browsers and servers
Key Concepts
Handshake: Initial HTTP upgrade request
Frame: Unit of data transmission
Connection: Persistent TCP connection
Message: Application-level data unit
Close: Graceful connection termination
Architecture
High-Level Architecture
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │────▶│ Client │────▶│ Client │
│ (Browser) │ │ (Mobile) │ │ (Desktop) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────────┴────────────────────┘
│
│ WebSocket Connection
│
▼
┌─────────────────────────┐
│ Load Balancer │
│ / API Gateway │
└──────┬──────────────────┘
│
▼
┌─────────────────────────┐
│ WebSocket Server │
│ │
│ ┌──────────┐ │
│ │Connection│ │
│ │ Manager │ │
│ └────┬─────┘ │
│ │ │
│ ┌────┴─────┐ │
│ │ Message │ │
│ │ Handler │ │
│ └──────────┘ │
└──────┬──────────────────┘
│
┌─────────────┴─────────────┐
│ │
┌──────▼──────┐ ┌───────▼──────┐
│ Backend │ │ Pub/Sub │
│ Services │ │ System │
└─────────────┘ └─────────────┘
Explanation:
- Clients: Web browsers, mobile apps, or desktop applications that establish WebSocket connections for real-time bidirectional communication.
- Load Balancer / API Gateway: Routes WebSocket connections and handles SSL termination.
- WebSocket Server: Maintains persistent connections with clients and handles message routing.
- Connection Manager: Manages WebSocket connections, handles connection lifecycle, and maintains connection pools.
- Message Handler: Processes incoming messages, routes them to appropriate handlers, and manages message framing.
- Backend Services: Business logic services and Pub/Sub systems that process messages and broadcast updates.
Core Architecture
┌─────────────────────────────────────────────────────────┐
│ WebSocket Clients │
│ (Browsers, Mobile Apps, Desktop Apps) │
└────────────────────┬────────────────────────────────────┘
│
│ HTTP Upgrade Request
│ (WebSocket Handshake)
│
┌────────────────────▼────────────────────────────────────┐
│ Load Balancer / API Gateway │
│ (SSL Termination, Routing) │
└────────────────────┬────────────────────────────────────┘
│
│ WebSocket Connection
│
┌────────────────────▼────────────────────────────────────┐
│ WebSocket Server │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Connection Manager │ │
│ │ (Connection Pool, Lifecycle Management) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Message Handler │ │
│ │ (Frame Parsing, Message Routing) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────┴─────────────────┐ │
│ │ │ │
│ ┌─────▼──────┐ ┌───────▼──────┐ │
│ │ Business │ │ Pub/Sub │ │
│ │ Logic │ │ System │ │
│ └────────────┘ └──────────────┘ │
│ │ │ │
│ └─────────────────┬─────────────────┘ │
│ │ │
│ ┌───────────────────────▼──────────────────────────┐ │
│ │ Backend Services │ │
│ │ (Database, Cache, Message Queue) │ │
│ └──────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
WebSocket Protocol
Handshake
Client Request:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Server Response:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Connection Lifecycle
1. HTTP Handshake
↓
2. Connection Established
↓
3. Data Exchange (Bidirectional)
↓
4. Close Handshake
↓
5. Connection Closed
Server Implementation
Node.js (ws library)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws, req) {
console.log('Client connected');
// Send message to client
ws.send('Welcome to WebSocket server');
// Receive message from client
ws.on('message', function incoming(message) {
console.log('Received: %s', message);
// Echo message back
ws.send(`Echo: ${message}`);
});
// Handle connection close
ws.on('close', function close() {
console.log('Client disconnected');
});
// Handle errors
ws.on('error', function error(err) {
console.error('WebSocket error:', err);
});
});
Python (websockets library)
import asyncio
import websockets
async def handle_client(websocket, path):
print('Client connected')
# Send welcome message
await websocket.send('Welcome to WebSocket server')
try:
async for message in websocket:
print(f'Received: {message}')
# Echo message back
await websocket.send(f'Echo: {message}')
except websockets.exceptions.ConnectionClosed:
print('Client disconnected')
async def main():
async with websockets.serve(handle_client, "localhost", 8080):
await asyncio.Future() # run forever
asyncio.run(main())
Go (gorilla/websocket)
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade error:", err)
return
}
defer conn.Close()
log.Println("Client connected")
// Send welcome message
conn.WriteMessage(websocket.TextMessage, []byte("Welcome"))
for {
// Read message
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Println("Read error:", err)
break
}
log.Printf("Received: %s", message)
// Echo message back
err = conn.WriteMessage(messageType, message)
if err != nil {
log.Println("Write error:", err)
break
}
}
}
func main() {
http.HandleFunc("/ws", handleWebSocket)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Client Implementation
JavaScript (Browser)
// Create WebSocket connection
const ws = new WebSocket('ws://localhost:8080');
// Connection opened
ws.onopen = function(event) {
console.log('Connected to server');
ws.send('Hello Server!');
};
// Receive message
ws.onmessage = function(event) {
console.log('Received:', event.data);
};
// Connection closed
ws.onclose = function(event) {
console.log('Connection closed');
};
// Error handling
ws.onerror = function(error) {
console.error('WebSocket error:', error);
};
// Send message
function sendMessage(message) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
}
Reconnection Logic
class WebSocketClient {
constructor(url) {
this.url = url;
this.ws = null;
this.reconnectInterval = 1000;
this.maxReconnectInterval = 30000;
this.reconnectDecay = 1.5;
this.shouldReconnect = true;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected');
this.reconnectInterval = 1000;
};
this.ws.onclose = () => {
if (this.shouldReconnect) {
this.reconnect();
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
reconnect() {
setTimeout(() => {
console.log('Reconnecting...');
this.connect();
this.reconnectInterval = Math.min(
this.reconnectInterval * this.reconnectDecay,
this.maxReconnectInterval
);
}, this.reconnectInterval);
}
send(message) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(message);
}
}
close() {
this.shouldReconnect = false;
if (this.ws) {
this.ws.close();
}
}
}
Message Types
Text Messages
// Send text
ws.send('Hello, Server!');
// Receive text
ws.onmessage = function(event) {
const message = event.data; // String
console.log('Text message:', message);
};
Binary Messages
// Send binary
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
ws.send(buffer);
// Receive binary
ws.onmessage = function(event) {
if (event.data instanceof ArrayBuffer) {
const view = new Uint8Array(event.data);
console.log('Binary message:', view);
}
};
JSON Messages
// Send JSON
const message = { type: 'chat', text: 'Hello' };
ws.send(JSON.stringify(message));
// Receive JSON
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
console.log('JSON message:', message);
};
Scaling Strategies
1. Sticky Sessions (Session Affinity)
Load Balancer Configuration:
Client → Load Balancer (Sticky Session) → WebSocket Server
Pros:
- Simple to implement
- Maintains connection
Cons:
- Uneven load distribution
- Server failure loses connections
2. Message Broker (Pub/Sub)
Architecture:
Client → WebSocket Server → Message Broker (Redis/Kafka) → Other Servers
Implementation:
const redis = require('redis');
const subscriber = redis.createClient();
const publisher = redis.createClient();
// Subscribe to channel
subscriber.subscribe('messages');
// Broadcast message
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
// Publish to all servers
publisher.publish('messages', message);
});
// Receive from other servers
subscriber.on('message', function(channel, message) {
ws.send(message);
});
});
Benefits:
- Horizontal scaling
- Message distribution
- Decoupled servers
3. Shared State (Redis)
Store Connection Info:
const redis = require('redis');
const client = redis.createClient();
// Store connection
wss.on('connection', function connection(ws, req) {
const userId = getUserId(req);
// Store connection mapping
client.set(`ws:${userId}`, ws.id);
ws.on('close', function() {
client.del(`ws:${userId}`);
});
});
// Send to specific user
async function sendToUser(userId, message) {
const serverId = await client.get(`ws:${userId}`);
if (serverId === currentServerId) {
// Send directly
} else {
// Forward to other server
forwardMessage(serverId, userId, message);
}
}
Performance Optimization
Connection Pooling
Reuse Connections:
class ConnectionPool {
constructor(maxConnections = 100) {
this.connections = new Map();
this.maxConnections = maxConnections;
}
add(userId, ws) {
if (this.connections.size >= this.maxConnections) {
// Remove oldest connection
const firstKey = this.connections.keys().next().value;
this.connections.get(firstKey).close();
this.connections.delete(firstKey);
}
this.connections.set(userId, ws);
}
get(userId) {
return this.connections.get(userId);
}
remove(userId) {
this.connections.delete(userId);
}
}
Heartbeat/Ping-Pong
Keep Connection Alive:
// Server
setInterval(function ping() {
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) {
return ws.terminate();
}
ws.isAlive = false;
ws.ping();
});
}, 30000);
ws.on('pong', function() {
this.isAlive = true;
});
// Client
ws.on('pong', function() {
console.log('Pong received');
});
setInterval(function() {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
}
}, 30000);
Message Batching
Batch Messages:
class MessageBatcher {
constructor(batchSize = 10, batchInterval = 100) {
this.batch = [];
this.batchSize = batchSize;
this.batchInterval = batchInterval;
}
add(message) {
this.batch.push(message);
if (this.batch.length >= this.batchSize) {
this.flush();
} else {
setTimeout(() => this.flush(), this.batchInterval);
}
}
flush() {
if (this.batch.length > 0) {
ws.send(JSON.stringify(this.batch));
this.batch = [];
}
}
}
Security
Authentication
Token-Based Auth:
const jwt = require('jsonwebtoken');
wss.on('connection', function connection(ws, req) {
const token = new URL(req.url, 'http://localhost').searchParams.get('token');
try {
const decoded = jwt.verify(token, SECRET);
ws.userId = decoded.userId;
} catch (err) {
ws.close(1008, 'Invalid token');
return;
}
// Handle connection
});
Rate Limiting
Limit Messages:
const rateLimit = new Map();
wss.on('connection', function connection(ws) {
const clientId = ws._socket.remoteAddress;
rateLimit.set(clientId, { count: 0, resetTime: Date.now() + 60000 });
ws.on('message', function incoming(message) {
const limit = rateLimit.get(clientId);
if (Date.now() > limit.resetTime) {
limit.count = 0;
limit.resetTime = Date.now() + 60000;
}
if (limit.count >= 100) {
ws.close(1008, 'Rate limit exceeded');
return;
}
limit.count++;
// Process message
});
});
Best Practices
1. Connection Management
- Implement reconnection logic
- Use heartbeat/ping-pong
- Handle connection errors gracefully
- Clean up on disconnect
2. Message Handling
- Validate message format
- Handle binary and text messages
- Implement message queuing
- Batch messages when possible
3. Scaling
- Use message broker for multi-server
- Implement sticky sessions if needed
- Monitor connection count
- Plan for horizontal scaling
4. Security
- Authenticate connections
- Implement rate limiting
- Use WSS (WebSocket Secure)
- Validate input
What Interviewers Look For
Real-Time Communication Understanding
- WebSocket Concepts
- Understanding of protocol, handshake
- Connection lifecycle
- Message handling
- Red Flags: No WebSocket understanding, wrong protocol, poor connection handling
- Scaling Challenges
- Horizontal scaling strategies
- Message distribution
- State management
- Red Flags: No scaling plan, single server, poor distribution
- Performance
- Connection management
- Message batching
- Heartbeat mechanism
- Red Flags: No optimization, poor performance, no heartbeat
Problem-Solving Approach
- Connection Management
- Reconnection logic
- Error handling
- Connection pooling
- Red Flags: No reconnection, poor error handling, no pooling
- Scaling Design
- Multi-server architecture
- Message distribution
- State synchronization
- Red Flags: No scaling, poor architecture, no synchronization
System Design Skills
- Real-Time Architecture
- WebSocket server design
- Message broker integration
- Load balancing
- Red Flags: No architecture, poor design, no load balancing
- Scalability
- Horizontal scaling
- Message distribution
- Performance optimization
- Red Flags: No scaling, poor distribution, no optimization
Communication Skills
- Clear Explanation
- Explains WebSocket concepts
- Discusses trade-offs
- Justifies design decisions
- Red Flags: Unclear explanations, no justification, confusing
Meta-Specific Focus
- Real-Time Systems Expertise
- Understanding of real-time communication
- WebSocket mastery
- Scaling patterns
- Key: Demonstrate real-time systems expertise
- System Design Skills
- Can design real-time systems
- Understands scaling challenges
- Makes informed trade-offs
- Key: Show practical real-time design skills
Summary
WebSocket Key Points:
- Full-Duplex: Bidirectional real-time communication
- Low Latency: Minimal overhead after handshake
- Persistent Connection: Single TCP connection
- Scaling: Message broker, sticky sessions, shared state
- Performance: Connection pooling, heartbeat, batching
Common Use Cases:
- Chat applications
- Live notifications
- Real-time gaming
- Collaborative editing
- Live dashboards
- Trading platforms
Best Practices:
- Implement reconnection logic
- Use heartbeat/ping-pong
- Handle errors gracefully
- Scale with message broker
- Authenticate connections
- Implement rate limiting
- Batch messages when possible
WebSocket is essential for building real-time applications that require low-latency, bidirectional communication.