%% fig-alt: "WebSocket connection state machine showing transitions between CONNECTING, OPEN, CLOSING, and CLOSED states. The diagram illustrates the full lifecycle from initial connection through graceful termination."
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22'}}}%%
stateDiagram-v2
[*] --> CLOSED: Initial State
CLOSED --> CONNECTING: new WebSocket(url)
CONNECTING --> OPEN: HTTP 101 Switching Protocols
CONNECTING --> CLOSED: Connection failed
OPEN --> OPEN: Send/Receive frames
OPEN --> CLOSING: close() called
OPEN --> CLOSED: Connection lost
CLOSING --> CLOSED: Close handshake complete
note right of CONNECTING
Sending HTTP Upgrade request
Sec-WebSocket-Key header
end note
note right of OPEN
Full-duplex communication
Ping/Pong heartbeat
end note
761 WebSocket Connection Animation
Interactive Visualization of Full-Duplex Real-Time Communication
761.1 WebSocket Connection Lifecycle
This interactive animation demonstrates how WebSocket establishes and maintains full-duplex, real-time connections. Watch the complete lifecycle from HTTP upgrade handshake through bidirectional messaging, heartbeat/keepalive, and graceful close sequences.
WebSocket provides:
- Full-Duplex Communication: Both client and server can send data simultaneously without polling
- Low Overhead: After initial handshake, frames have minimal headers (2-14 bytes)
- Real-Time Updates: Push data instantly without repeated HTTP requests
- Persistent Connection: Single TCP connection for the entire session
- Firewall Friendly: Uses HTTP ports (80/443) for easier traversal
IoT Use Cases: Real-time dashboards, live sensor feeds, remote device control, chat systems, collaborative applications.
- Connection States: Watch the connection progress through CONNECTING, OPEN, CLOSING, CLOSED
- Frame Types: Select different frame types (text, binary, ping, pong, close) to send
- Heartbeat Config: Adjust ping interval and timeout for keepalive demonstration
- Reconnection: See exponential backoff retry strategies in action
- Play Controls: Use Play/Pause and Step buttons to control animation speed
- Scenarios: Try pre-built scenarios for common WebSocket patterns
761.2 WebSocket Protocol Overview
WebSocket (RFC 6455) provides full-duplex communication channels over a single TCP connection. Unlike HTTP’s request-response model, WebSocket allows both client and server to send data at any time.
761.2.1 Connection States
761.2.2 The WebSocket Handshake
WebSocket connections begin with an HTTP Upgrade request:
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=
The Sec-WebSocket-Key is a random base64-encoded value. The server combines it with a GUID and returns the SHA-1 hash in Sec-WebSocket-Accept. This prevents cache proxy attacks but is not for authentication.
761.3 Frame Types
| Opcode | Type | Description | Use Case |
|---|---|---|---|
| 0x1 | TEXT | UTF-8 encoded text data | JSON messages, chat |
| 0x2 | BINARY | Raw binary data | Images, files, protobuf |
| 0x8 | CLOSE | Connection close request | Graceful termination |
| 0x9 | PING | Heartbeat request | Keepalive, latency check |
| 0xA | PONG | Heartbeat response | Automatic reply to PING |
This diagram shows the binary structure of a WebSocket frame as defined in RFC 6455.
%% fig-alt: Diagram showing WebSocket frame binary structure with FIN bit, opcode, mask flag, payload length, masking key, and payload data fields
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22'}}}%%
flowchart TB
subgraph Frame["WebSocket Frame Structure"]
direction LR
subgraph B1["Byte 1"]
FIN["FIN<br/>1 bit"]
RSV["RSV<br/>3 bits"]
OP["Opcode<br/>4 bits"]
end
subgraph B2["Byte 2"]
MASK["Mask<br/>1 bit"]
LEN["Length<br/>7 bits"]
end
subgraph Extended["Extended (optional)"]
ELEN["Extended<br/>Length"]
MKEY["Masking<br/>Key"]
end
subgraph Payload["Payload"]
DATA["Application<br/>Data"]
end
end
B1 --> B2 --> Extended --> Payload
style Frame fill:#2C3E50,color:#fff
style B1 fill:#16A085,color:#fff
style B2 fill:#E67E22,color:#fff
style Extended fill:#9B59B6,color:#fff
style Payload fill:#7F8C8D,color:#fff
761.3.1 Frame Structure (Simplified)
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 ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
761.4 Heartbeat and Keepalive
WebSocket connections can silently die due to:
- NAT timeout: Routers close idle connections (often 30-60 seconds)
- Proxy timeout: Load balancers drop idle connections
- Network changes: Mobile devices switching networks
- Half-open connections: Server crash leaving client unaware
- Ping Interval: 20-30 seconds (before typical NAT timeout)
- Pong Timeout: 5-10 seconds (detect dead connections quickly)
- Application-level Ping: Consider app-level heartbeats for data integrity
- Bidirectional Pings: Both client and server can initiate
// Client-side heartbeat example
const PING_INTERVAL = 30000; // 30 seconds
const PONG_TIMEOUT = 10000; // 10 seconds
let pingTimer, pongTimer;
function startHeartbeat() {
pingTimer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping', ts: Date.now() }));
pongTimer = setTimeout(() => {
console.error('Pong timeout - connection dead');
ws.close();
reconnect();
}, PONG_TIMEOUT);
}
}, PING_INTERVAL);
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'pong') {
clearTimeout(pongTimer);
const latency = Date.now() - msg.ts;
console.log(`RTT: ${latency}ms`);
}
};761.5 Reconnection Strategies
When WebSocket connections drop, implement robust reconnection logic:
761.5.1 Exponential Backoff
%% fig-alt: "Flowchart showing WebSocket reconnection strategy with exponential backoff. Shows retry attempts with increasing delays (1s, 2s, 4s, 8s, 16s) and maximum retry limit."
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22'}}}%%
flowchart TD
DISC[Connection Lost] --> WAIT1["Wait 1s"]
WAIT1 --> TRY1["Attempt 1"]
TRY1 -->|Success| OPEN[Connected!]
TRY1 -->|Fail| WAIT2["Wait 2s"]
WAIT2 --> TRY2["Attempt 2"]
TRY2 -->|Success| OPEN
TRY2 -->|Fail| WAIT4["Wait 4s"]
WAIT4 --> TRY3["Attempt 3"]
TRY3 -->|Success| OPEN
TRY3 -->|Fail| WAIT8["Wait 8s"]
WAIT8 --> TRY4["...continue"]
TRY4 -->|Max reached| FAIL[Give Up]
TRY4 -->|Success| OPEN
style DISC fill:#E74C3C,color:#fff
style OPEN fill:#27AE60,color:#fff
style FAIL fill:#7F8C8D,color:#fff
761.5.2 Reconnection Best Practices
| Strategy | Description | When to Use |
|---|---|---|
| Exponential Backoff | Double delay each attempt (1s, 2s, 4s, 8s…) | Default strategy |
| Jitter | Add randomness to prevent thundering herd | Multi-client systems |
| Max Retries | Stop after N attempts | Prevent infinite loops |
| Circuit Breaker | Stop retrying after repeated failures | Protect servers |
| Visibility Change | Reconnect when tab becomes visible | Browser apps |
class WebSocketReconnector {
constructor(url, options = {}) {
this.url = url;
this.maxRetries = options.maxRetries || 5;
this.baseDelay = options.baseDelay || 1000;
this.maxDelay = options.maxDelay || 30000;
this.retries = 0;
this.ws = null;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.retries = 0; // Reset on successful connection
console.log('Connected!');
};
this.ws.onclose = (event) => {
if (!event.wasClean && this.retries < this.maxRetries) {
const delay = Math.min(
this.baseDelay * Math.pow(2, this.retries),
this.maxDelay
);
// Add jitter (0-100ms)
const jitter = Math.random() * 100;
console.log(`Reconnecting in ${delay + jitter}ms...`);
setTimeout(() => {
this.retries++;
this.connect();
}, delay + jitter);
}
};
}
}761.6 WebSocket vs Other Protocols
| Feature | WebSocket | HTTP Long Polling | Server-Sent Events | MQTT over WS |
|---|---|---|---|---|
| Direction | Full-duplex | Half-duplex | Server-to-client | Full-duplex |
| Overhead | 2-14 bytes/frame | Full HTTP headers | Minimal | MQTT + WS headers |
| Latency | Very low | Higher | Low | Low |
| Reconnection | Manual | Automatic | Automatic | Handled by MQTT |
| Browser Support | All modern | All | Most modern | Via library |
| IoT Fit | Good | Poor | Good for push | Excellent |
Best For: - Real-time dashboards with bidirectional control - Browser-based device management - Live sensor visualization - Chat and collaboration features
Consider Alternatives: - MQTT: Better for constrained devices, built-in QoS - CoAP: Better for low-power wireless networks - Server-Sent Events: Simpler if you only need server push
761.7 IoT Use Cases
761.7.1 Real-Time Dashboard
%% fig-alt: "Architecture diagram showing IoT sensors sending data through MQTT broker to a WebSocket gateway, which streams to a browser dashboard for real-time visualization."
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22'}}}%%
flowchart LR
subgraph Devices["IoT Devices"]
S1["🌡️ Temp"]
S2["💧 Humidity"]
S3["💨 CO2"]
end
subgraph Backend["Backend"]
MQTT["MQTT Broker"]
WS["WebSocket<br/>Gateway"]
end
subgraph Browser["Browser Dashboard"]
UI["📊 Charts"]
CTRL["🎛️ Controls"]
end
S1 --> MQTT
S2 --> MQTT
S3 --> MQTT
MQTT --> WS
WS <-->|Full-duplex| UI
WS <-->|Commands| CTRL
style Devices fill:#16A085,color:#fff
style Backend fill:#2C3E50,color:#fff
style Browser fill:#E67E22,color:#fff
761.7.2 Remote Device Control
// Browser-side device control
const ws = new WebSocket('wss://iot.example.com/device/123');
ws.onopen = () => {
// Subscribe to device telemetry
ws.send(JSON.stringify({
type: 'subscribe',
topics: ['temperature', 'status']
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
updateDashboard(data);
};
// Send command to device
function setTemperature(value) {
ws.send(JSON.stringify({
type: 'command',
action: 'setTemperature',
value: value
}));
}761.8 Conceptual Diagrams
%% fig-alt: Process flow diagram showing WebSocket connection from HTTP upgrade handshake through bidirectional communication
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22', 'secondaryColor': '#ECF0F1', 'tertiaryColor': '#fff'}}}%%
flowchart TD
subgraph Handshake["HTTP Upgrade Handshake"]
A[Client opens TCP connection] --> B[Send HTTP Upgrade request]
B --> C[Server validates request]
C --> D[Server sends 101 Switching Protocols]
D --> E[WebSocket connection established]
end
subgraph Communication["Bidirectional Communication"]
E --> F{Who sends?}
F --> |Client|G[Client sends frame]
F --> |Server|H[Server sends frame]
G --> I[Frame delivered to server]
H --> J[Frame delivered to client]
I --> F
J --> F
end
subgraph Maintenance["Connection Maintenance"]
K[Ping frame sent] --> L[Pong frame returned]
L --> M[Connection healthy]
M --> K
end
subgraph Close["Connection Close"]
N[Close frame sent] --> O[Close frame acknowledged]
O --> P[TCP connection closed]
end
style Handshake fill:#2C3E50,color:#fff
style Communication fill:#16A085,color:#fff
style Maintenance fill:#E67E22,color:#fff
style Close fill:#7F8C8D,color:#fff
%% fig-alt: State diagram showing WebSocket connection states from CONNECTING through OPEN to CLOSED
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22'}}}%%
stateDiagram-v2
[*] --> CONNECTING: new WebSocket(url)
CONNECTING --> OPEN: 101 Switching Protocols
CONNECTING --> CLOSED: Connection failed
OPEN --> OPEN: Send/receive frames
OPEN --> CLOSING: close() called
OPEN --> CLOSED: Connection lost
CLOSING --> CLOSED: Close handshake complete
CLOSED --> [*]
note right of CONNECTING : readyState = 0
note right of OPEN : readyState = 1
note right of CLOSING : readyState = 2
note right of CLOSED : readyState = 3
%% fig-alt: Comparison diagram showing WebSocket persistent connection versus HTTP request-response cycle
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22'}}}%%
flowchart LR
subgraph HTTP["HTTP Polling"]
H1[Request] --> H2[Response]
H3[Request] --> H4[Response]
H5[Request] --> H6[Response]
H7[Overhead per message]
end
subgraph WS["WebSocket"]
W1[Upgrade once] --> W2[Message]
W2 --> W3[Message]
W3 --> W4[Message]
W4 --> W5[Message]
W6[Low overhead per message]
end
Compare[Choose Based On]
Compare --> |Frequent updates|WS
Compare --> |Rare updates|HTTP
HTTP --> Use1[Use for:<br/>REST APIs<br/>Infrequent data<br/>Request-response]
WS --> Use2[Use for:<br/>Real-time data<br/>IoT dashboards<br/>Live updates]
style HTTP fill:#7F8C8D,color:#fff
style WS fill:#16A085,color:#fff
%% fig-alt: Architecture diagram showing WebSocket frame structure with opcode, payload, and masking
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22'}}}%%
flowchart TB
subgraph Frame["WebSocket Frame"]
direction LR
FIN[FIN<br/>1 bit]
RSV[RSV1-3<br/>3 bits]
OP[Opcode<br/>4 bits]
MASK[Mask<br/>1 bit]
LEN[Payload Length<br/>7+ bits]
KEY[Masking Key<br/>0 or 4 bytes]
DATA[Payload Data]
end
subgraph Opcodes["Frame Types"]
T0[0x0: Continuation]
T1[0x1: Text]
T2[0x2: Binary]
T8[0x8: Close]
T9[0x9: Ping]
TA[0xA: Pong]
end
Frame --> Opcodes
style Frame fill:#2C3E50,color:#fff
style Opcodes fill:#16A085,color:#fff
761.9 What’s Next
- MQTT Pub/Sub Animation - Compare with MQTT’s publish/subscribe model
- TCP Handshake Animation - Understand the underlying TCP connection
- MQTT over WebSocket - Using MQTT in browsers
- Stream Processing - Processing real-time IoT data
- Simulations Hub - More interactive visualizations
This animation is implemented in approximately 800 lines of Observable JavaScript. Key features:
- Connection State Machine: Accurate representation of WebSocket readyState (CONNECTING=0, OPEN=1, CLOSING=2, CLOSED=3)
- Frame Type Visualization: All RFC 6455 frame types with color coding
- Heartbeat Simulation: Configurable ping interval with timeout detection
- Reconnection Demo: Exponential backoff with visual countdown
- Play/Pause Controls: Speed adjustment and step-through mode
- Event Logging: Real-time log of all WebSocket events
- IEEE Colors: Consistent palette throughout
The animation demonstrates the complete WebSocket lifecycle from handshake through graceful close, with emphasis on the reliability features critical for IoT applications.