Skip to content
Back to skills

600 Reference Bb26a7de

ASecurity

1. [Protocol Fundamentals](#protocol-fundamentals) 2. [Connection Lifecycle](#connection-lifecycle) 3. [Frame Structure](#frame-structure) 4. [Message Types](#message-types) 5. [Security](#security) 6. [Scaling Patterns](#scaling-patterns) 7. [Reconnection Strategies](#reconnection-strategies) 8. [Server Implementations](#server-implementations) 9. [Client Implementations](#client-implementations) 10. [Testing Strategies](#testing-strategies) 11. [Common Patterns](#common-patterns) 12. [Comparis

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
developmentjavascripttypescriptpythonrustgojavabashreactnodeexpress

Works with

  • cli
  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 600-reference_bb26a7de --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 600 Reference Bb26a7de?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 600 Reference Bb26a7de
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-600-reference-bb26a7de/badge)](https://www.skillsdirectory.com/skills/tools-only-600-reference-bb26a7de)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# WebSocket Protocol - Comprehensive Reference

## Table of Contents

1. [Protocol Fundamentals](#protocol-fundamentals)
2. [Connection Lifecycle](#connection-lifecycle)
3. [Frame Structure](#frame-structure)
4. [Message Types](#message-types)
5. [Security](#security)
6. [Scaling Patterns](#scaling-patterns)
7. [Reconnection Strategies](#reconnection-strategies)
8. [Server Implementations](#server-implementations)
9. [Client Implementations](#client-implementations)
10. [Testing Strategies](#testing-strategies)
11. [Common Patterns](#common-patterns)
12. [Comparison with Alternatives](#comparison-with-alternatives)
13. [Production Best Practices](#production-best-practices)
14. [Troubleshooting](#troubleshooting)
15. [Performance Optimization](#performance-optimization)

---

## Protocol Fundamentals

### RFC 6455 Overview

WebSocket is a computer communications protocol providing full-duplex communication channels over a single TCP connection. Standardized by RFC 6455 in 2011, it enables real-time, bidirectional data exchange between client and server with lower overhead than HTTP polling.

**Key Characteristics**:
- Full-duplex bidirectional communication
- Single TCP connection
- Low overhead (2-14 bytes per frame vs HTTP headers)
- Works over HTTP ports (80/443)
- Upgrades from HTTP via handshake
- UTF-8 text or binary data
- Built-in ping/pong for keepalive

**Protocol URI Schemes**:
```
ws://  - Unencrypted WebSocket (like HTTP)
wss:// - Encrypted WebSocket over TLS (like HTTPS)
```

### WebSocket vs HTTP

**HTTP Request/Response**:
```
Client -> Server: GET /data HTTP/1.1
Server -> Client: 200 OK + data
[Connection closes or kept alive]
Client -> Server: New request needed for new data
```

**WebSocket Persistent Connection**:
```
Client -> Server: Upgrade to WebSocket
Server -> Client: 101 Switching Protocols
[Connection remains open]
Client <-> Server: Bidirectional messages anytime
```

**Overhead Comparison**:
```
HTTP Request Headers:  ~200-800 bytes per request
HTTP Response Headers: ~200-500 bytes per response
Total per exchange:    ~400-1300 bytes

WebSocket Frame:       2-14 bytes per message
```

For 1000 messages:
- HTTP: 400KB - 1.3MB overhead
- WebSocket: 2KB - 14KB overhead

### Protocol Layers

```
┌─────────────────────────────────┐
│   Application Layer             │
│   (Your application logic)      │
├─────────────────────────────────┤
│   WebSocket Protocol            │
│   (RFC 6455 - Framing/Messages) │
├─────────────────────────────────┤
│   TLS (for wss://)              │
│   (Optional encryption)         │
├─────────────────────────────────┤
│   TCP                           │
│   (Reliable transport)          │
├─────────────────────────────────┤
│   IP                            │
│   (Network routing)             │
└─────────────────────────────────┘
```

### Use Cases

**Ideal for**:
- Real-time chat applications
- Live sports/stock tickers
- Multiplayer games
- Collaborative editing (Google Docs-style)
- Live dashboards/monitoring
- IoT device communication
- Live video streaming metadata
- Trading platforms
- Real-time notifications

**Not ideal for**:
- One-way server push (use SSE)
- Infrequent updates (use HTTP polling)
- REST API calls (use HTTP)
- File uploads (use HTTP multipart)
- Heavy proxy/firewall environments

---

## Connection Lifecycle

### Phase 1: HTTP Upgrade Handshake

The WebSocket connection begins as an HTTP request that "upgrades" to the WebSocket protocol.

**Client Handshake Request**:
```http
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: [REDACTED]
Sec-WebSocket-Version: 13
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Extensions: permessage-deflate
```

**Header Breakdown**:
- `Upgrade: websocket` - Request protocol upgrade
- `Connection: Upgrade` - Indicate connection will be upgraded
- `Sec-WebSocket-Key` - Random base64-encoded 16-byte value (security)
- `Sec-WebSocket-Version` - WebSocket protocol version (13 is current)
- `Origin` - Browser security (prevent CSRF)
- `Sec-WebSocket-Protocol` - Subprotocols (optional)
- `Sec-WebSocket-Extensions` - Extensions like compression (optional)

**Server Handshake Response**:
```http
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Extensions: permessage-deflate
```

**Header Breakdown**:
- `101 Switching Protocols` - Upgrade accepted
- `Sec-WebSocket-Accept` - Computed from client's key (see below)
- `Sec-WebSocket-Protocol` - Selected subprotocol (if any)
- `Sec-WebSocket-Extensions` - Accepted extensions

**Sec-WebSocket-Accept Calculation**:
```javascript
// Server computes this from client's Sec-WebSocket-Key
const crypto = require('crypto');

function generateAcceptKey(clientKey) {
  const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; // RFC 6455 magic string
  const hash = crypto
    .createHash('sha1')
    .update(clientKey + GUID)
    .digest('base64');
  return hash;
}

// Example:
const clientKey = '[REDACTED]';
const acceptKey = generateAcceptKey(clientKey);
// Returns: 's3pPLMBiTxaQ9kYGzzhZRbK+xOo='
```

This prevents accidental WebSocket connections and ensures both sides understand the protocol.

### Phase 2: Data Transfer

Once upgraded, the connection switches to WebSocket framing. Both client and server can send messages at any time.

**Message Flow**:
```
Client                          Server
  |                               |
  |-- Text Message -------------->|
  |                               |
  |<-- Text Message --------------|
  |                               |
  |-- Binary Message ------------>|
  |                               |
  |<-- Ping -----------------------|
  |-- Pong ---------------------->|
  |                               |
```

**Multiplexing**: A single WebSocket connection can carry multiple logical channels/topics by using message prefixes or JSON envelopes:

```javascript
// Topic-based routing
ws.send(JSON.stringify({
  topic: 'chat',
  data: { user: 'Alice', message: 'Hello' }
}));

ws.send(JSON.stringify({
  topic: 'notifications',
  data: { type: 'friend_request', from: 'Bob' }
}));
```

### Phase 3: Connection Close

Either side can initiate a close. The closing handshake ensures graceful shutdown.

**Close Handshake**:
```
Client                          Server
  |                               |
  |-- Close Frame (code 1000) --->|
  |                               |
  |<-- Close Frame (code 1000) ---|
  |                               |
  [TCP connection closes]
```

**Close Codes** (from RFC 6455):
```
1000 - Normal Closure
1001 - Going Away (server shutdown, browser navigation)
1002 - Protocol Error
1003 - Unsupported Data (e.g., text-only but received binary)
1006 - Abnormal Closure (no close frame, connection lost)
1007 - Invalid Payload Data (malformed UTF-8 in text)
1008 - Policy Violation (generic)
1009 - Message Too Big
1010 - Mandatory Extension (client expected extension)
1011 - Internal Server Error
1015 - TLS Handshake Failure
```

**Application-Defined Codes**: 3000-4999 range for custom close reasons.

**Close Sequence**:
```javascript
// Client initiates close
ws.close(1000, 'User logged out');

// Server receives close event
ws.on('close', (code, reason) => {
  console.log(`Closed: ${code} - ${reason}`);
  // Cleanup resources
});

// Server responds with close frame
// TCP connection terminates
```

### State Transitions

```
CONNECTING (0)
    ↓
    [Handshake completes]
    ↓
OPEN (1)
    ↓
    [Close initiated]
    ↓
CLOSING (2)
    ↓
    [Close handshake completes]
    ↓
CLOSED (3)
```

**Browser WebSocket API States**:
```javascript
WebSocket.CONNECTING  // 0
WebSocket.OPEN        // 1
WebSocket.CLOSING     // 2
WebSocket.CLOSED      // 3

// Check state
if (ws.readyState === WebSocket.OPEN) {
  ws.send('message');
}
```

### Connection Errors

**Common Failure Modes**:
1. **Handshake Rejection**: Server returns non-101 status
2. **Network Interruption**: Connection drops without close frame
3. **Timeout**: No data received for extended period
4. **Protocol Violation**: Invalid frame received
5. **TLS Failure**: Certificate issues on wss://

**Error Handling**:
```javascript
ws.on('error', (error) => {
  console.error('WebSocket error:', error);
  // Attempt reconnection if appropriate
});

ws.on('close', (code, reason) => {
  if (code !== 1000) {
    console.error(`Abnormal close: ${code} - ${reason}`);
    // Trigger reconnection logic
  }
});
```

---

## Frame Structure

WebSocket uses a binary framing protocol. Each message is split into one or more frames.

### Frame Format

```
 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 ...                |
+---------------------------------------------------------------+
```

### Field Descriptions

**FIN (1 bit)**:
- `1` = Final fragment in message
- `0` = More fragments follow
- Allows message fragmentation

**RSV1, RSV2, RSV3 (1 bit each)**:
- Reserved for extensions
- Must be 0 unless extension defines usage
- Example: RSV1 = 1 for permessage-deflate compression

**Opcode (4 bits)**:
```
0x0 - Continuation frame
0x1 - Text frame (UTF-8)
0x2 - Binary frame
0x8 - Connection close
0x9 - Ping
0xA - Pong
0x3-0x7 - Reserved for future data frames
0xB-0xF - Reserved for future control frames
```

**MASK (1 bit)**:
- `1` = Payload is masked (MUST be 1 for client->server)
- `0` = Payload is not masked (server->client)
- Prevents cache poisoning attacks

**Payload Length (7 bits, 7+16 bits, or 7+64 bits)**:
```
0-125:  Actual payload length
126:    Next 2 bytes are 16-bit length
127:    Next 8 bytes are 64-bit length
```

**Masking Key (0 or 4 bytes)**:
- Present if MASK bit is set
- Random 32-bit value
- Used to XOR payload data

**Payload Data**:
- Extension data (if negotiated) + Application data
- Masked if from client

### Masking Algorithm

Client-to-server frames MUST be masked to prevent cache poisoning:

```javascript
function maskPayload(payload, maskingKey) {
  const masked = Buffer.alloc(payload.length);
  for (let i = 0; i < payload.length; i++) {
    masked[i] = payload[i] ^ maskingKey[i % 4];
  }
  return masked;
}

// Example
const payload = Buffer.from('Hello');
const maskingKey = Buffer.from([0x12, 0x34, 0x56, 0x78]);
const masked = maskPayload(payload, maskingKey);

// Unmask (server-side)
const unmasked = maskPayload(masked, maskingKey); // XOR is reversible
// unmasked equals original payload
```

### Frame Examples

**Simple Text Message** (client->server):
```
"Hi" (2 bytes payload)

0x81 0x82 [mask] [masked payload]

Breakdown:
0x81 = 10000001
  - FIN=1 (final fragment)
  - RSV=000
  - Opcode=0001 (text)

0x82 = 10000010
  - MASK=1
  - Length=2

[4 bytes masking key]
[2 bytes masked payload]

Total: 8 bytes
```

**Ping Frame** (server->client):
```
0x89 0x00

Breakdown:
0x89 = 10001001
  - FIN=1
  - RSV=000
  - Opcode=1001 (ping)

0x00 = 00000000
  - MASK=0
  - Length=0

Total: 2 bytes
```

**Large Binary Message** (server->client, 65KB):
```
0x82 0x7E [2-byte length] [payload]

Breakdown:
0x82 = 10000010
  - FIN=1
  - Opcode=0010 (binary)

0x7E = 01111110
  - MASK=0
  - Length=126 (indicates next 2 bytes are length)

[2 bytes: 0xFF 0xFF = 65535]
[65535 bytes payload]

Total: 4 + 65535 = 65539 bytes
```

### Fragmentation

Large messages can be split into multiple frames:

```
Frame 1: FIN=0, Opcode=0x1 (text), Payload="Hello "
Frame 2: FIN=0, Opcode=0x0 (continuation), Payload="World"
Frame 3: FIN=1, Opcode=0x0 (continuation), Payload="!"

Reassembled: "Hello World!"
```

**Fragmentation Rules**:
- First frame has message opcode (text/binary)
- Subsequent frames have opcode 0x0 (continuation)
- Control frames (ping/pong/close) can be interleaved
- Control frames MUST NOT be fragmented

**Example with Interleaved Ping**:
```
Frame 1: FIN=0, Opcode=0x1 (text), Payload="Start"
Frame 2: FIN=1, Opcode=0x9 (ping), Payload=""
Frame 3: FIN=1, Opcode=0xA (pong), Payload=""
Frame 4: FIN=1, Opcode=0x0 (continuation), Payload="End"

Text message: "StartEnd"
Ping/Pong: Handled separately
```

### Control Frames

**Characteristics**:
- Opcode >= 0x8
- MUST NOT be fragmented
- Payload <= 125 bytes
- Can be injected mid-message

**Close Frame Payload**:
```
[2-byte status code] [UTF-8 reason]

Example:
0x03 0xE8 "Going away"
  = 1000 "Going away"
```

**Ping/Pong Application Data**:
```
Ping: 0x89 0x05 "12345"
Pong: 0x8A 0x05 "12345"  (echoes ping data)
```

---

## Message Types

### Text Messages

**Characteristics**:
- Opcode: 0x1
- Payload MUST be valid UTF-8
- Server MUST close connection on invalid UTF-8 (code 1007)

**Sending**:
```javascript
// Browser
ws.send('Hello World');
ws.send(JSON.stringify({ type: 'chat', msg: 'Hi' }));

// Node.js (ws library)
ws.send('Hello World', { binary: false });
```

**Receiving**:
```javascript
ws.onmessage = (event) => {
  if (typeof event.data === 'string') {
    console.log('Text:', event.data);
    const obj = JSON.parse(event.data);
  }
};
```

**UTF-8 Validation**:
```javascript
// Server must validate
function isValidUTF8(buffer) {
  try {
    buffer.toString('utf8');
    return true;
  } catch (e) {
    return false;
  }
}

ws.on('message', (data, isBinary) => {
  if (!isBinary && !isValidUTF8(data)) {
    ws.close(1007, 'Invalid UTF-8');
  }
});
```

**Common JSON Envelope Pattern**:
```javascript
// Structured message format
const message = {
  type: 'event_type',
  id: 'unique_id',
  timestamp: Date.now(),
  data: { /* payload */ }
};

ws.send(JSON.stringify(message));
```

### Binary Messages

**Characteristics**:
- Opcode: 0x2
- Raw binary data
- No encoding constraints

**Sending**:
```javascript
// Browser - ArrayBuffer
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setUint32(0, 12345678);
ws.send(buffer);

// Browser - Blob
const blob = new Blob(['binary data'], { type: 'application/octet-stream' });
ws.send(blob);

// Node.js - Buffer
const buf = Buffer.from([0x01, 0x02, 0x03]);
ws.send(buf, { binary: true });
```

**Receiving**:
```javascript
// Browser
ws.binaryType = 'arraybuffer'; // or 'blob'

ws.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    const view = new DataView(event.data);
    const value = view.getUint32(0);
  }
};

// Node.js
ws.on('message', (data, isBinary) => {
  if (isBinary) {
    console.log('Binary:', data); // Buffer
  }
});
```

**Use Cases**:
- Image/video streaming
- Audio data
- Protocol buffers / MessagePack
- Game state (packed binary formats)
- Sensor data

### Ping/Pong Frames

**Purpose**:
- Keep connection alive
- Detect broken connections
- Measure latency

**Ping Frame**:
- Opcode: 0x9
- Optional payload (max 125 bytes)
- Recipient MUST respond with Pong

**Pong Frame**:
- Opcode: 0xA
- MUST echo Ping payload
- Can be sent unsolicited (heartbeat)

**Browser Limitation**: JavaScript WebSocket API doesn't expose ping/pong directly (browser handles automatically).

**Server-Side Ping** (Node.js ws):
```javascript
const ws = new WebSocket.Server({ port: 8080 });

ws.on('connection', (socket) => {
  const interval = setInterval(() => {
    if (socket.readyState === WebSocket.OPEN) {
      socket.ping();
    }
  }, 30000); // Every 30 seconds

  socket.on('pong', () => {
    console.log('Pong received');
  });

  socket.on('close', () => {
    clearInterval(interval);
  });
});
```

**Client-Side Pong** (automatic in most libraries):
```javascript
// Most libraries auto-respond to ping
// Manual pong (if needed):
ws.on('ping', (data) => {
  ws.pong(data);
});
```

**Latency Measurement**:
```javascript
let pingStart;

const interval = setInterval(() => {
  pingStart = Date.now();
  ws.ping();
}, 5000);

ws.on('pong', () => {
  const latency = Date.now() - pingStart;
  console.log(`Latency: ${latency}ms`);
});
```

**Timeout Detection**:
```javascript
const PING_INTERVAL = 30000;
const PONG_TIMEOUT = 5000;

let pingTimeout;

function heartbeat() {
  clearTimeout(pingTimeout);
  pingTimeout = setTimeout(() => {
    console.log('Connection timed out');
    ws.terminate(); // Force close
  }, PONG_TIMEOUT);
}

const interval = setInterval(() => {
  ws.ping();
  heartbeat();
}, PING_INTERVAL);

ws.on('pong', () => {
  heartbeat();
});

ws.on('close', () => {
  clearInterval(interval);
  clearTimeout(pingTimeout);
});
```

### Close Frames

**Close Frame Structure**:
```
[2-byte status code] [optional UTF-8 reason]
```

**Common Patterns**:
```javascript
// Normal closure
ws.close(1000, 'Goodbye');

// Application error
ws.close(4000, 'Authentication failed');

// Policy violation
ws.close(1008, 'Rate limit exceeded');

// Going away
ws.close(1001, 'Server shutting down');
```

**Graceful Shutdown**:
```javascript
// Server shutdown
process.on('SIGTERM', () => {
  wss.clients.forEach((ws) => {
    ws.close(1001, 'Server shutting down');
  });

  setTimeout(() => {
    process.exit(0);
  }, 5000);
});
```

**Close Handshake Timeout**:
```javascript
ws.on('close', (code, reason) => {
  console.log(`Closed: ${code} - ${reason}`);
});

// Force close if handshake takes too long
const closeTimeout = setTimeout(() => {
  ws.terminate(); // Forceful close
}, 3000);

ws.on('close', () => {
  clearTimeout(closeTimeout);
});
```

---

## Security

### Origin Validation

The `Origin` header (sent by browsers) must be validated to prevent CSRF attacks.

**Attack Scenario**:
```
1. User logged into trusted.com (has WebSocket connection)
2. User visits evil.com
3. evil.com JavaScript tries: new WebSocket('wss://trusted.com/ws')
4. Without origin check, connection succeeds with user's cookies
```

**Server-Side Validation**:
```javascript
const ALLOWED_ORIGINS = [
  'https://trusted.com',
  'https://app.trusted.com'
];

wss.on('connection', (ws, req) => {
  const origin = req.headers.origin;

  if (!ALLOWED_ORIGINS.includes(origin)) {
    ws.close(1008, 'Origin not allowed');
    return;
  }

  // Continue with connection
});
```

**Development vs Production**:
```javascript
const ALLOWED_ORIGINS = process.env.NODE_ENV === 'production'
  ? ['https://app.example.com']
  : ['http://localhost:3000', 'http://localhost:5173'];
```

### Authentication

WebSocket doesn't have built-in authentication. Common patterns:

**1. Token in Query String**:
```javascript
// Client
const token = await getAuthToken();
const ws = new WebSocket(`wss://api.example.com/ws?token=${token}`);

// Server
wss.on('connection', (ws, req) => {
  const url = new URL(req.url, 'wss://api.example.com');
  const token = url.searchParams.get('token');

  if (!verifyToken(token)) {
    ws.close(1008, 'Invalid token');
    return;
  }

  ws.userId = extractUserId(token);
});
```

**Caution**: Tokens in URLs may be logged. Use wss:// to encrypt.

**2. Token in Subprotocol**:
```javascript
// Client
const ws = new WebSocket('wss://api.example.com/ws', ['access_token', token]);

// Server
wss.on('connection', (ws, req) => {
  const protocol = req.headers['sec-websocket-protocol'];
  const [_, token] = protocol.split(', ');

  if (!verifyToken(token)) {
    ws.close(1008, 'Invalid token');
    return;
  }
});
```

**3. Cookie-Based** (if same-origin):
```javascript
// Cookies sent automatically in handshake
wss.on('connection', (ws, req) => {
  const cookies = parseCookies(req.headers.cookie);
  const sessionId = cookies['session_id'];

  if (!validateSession(sessionId)) {
    ws.close(1008, 'Invalid session');
    return;
  }
});
```

**4. Post-Connection Authentication**:
```javascript
// Client
const ws = new WebSocket('wss://api.example.com/ws');

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'auth',
    token: authToken
  }));
};

// Server
const pendingAuth = new WeakMap();

wss.on('connection', (ws) => {
  pendingAuth.set(ws, setTimeout(() => {
    ws.close(1008, 'Authentication timeout');
  }, 5000));

  ws.on('message', (data) => {
    const msg = JSON.parse(data);

    if (msg.type === 'auth' && pendingAuth.has(ws)) {
      clearTimeout(pendingAuth.get(ws));
      pendingAuth.delete(ws);

      if (verifyToken(msg.token)) {
        ws.authenticated = true;
        ws.userId = extractUserId(msg.token);
      } else {
        ws.close(1008, 'Invalid credentials');
      }
    } else if (!ws.authenticated) {
      ws.close(1008, 'Not authenticated');
    }
  });
});
```

### TLS/SSL (wss://)

**Always use wss:// in production**:
- Encrypts all data
- Prevents eavesdropping
- Prevents man-in-the-middle attacks
- Required for browser security features

**Certificate Setup**:
```javascript
const https = require('https');
const WebSocket = require('ws');
const fs = require('fs');

const server = https.createServer({
  cert: fs.readFileSync('/path/to/cert.pem'),
  key: fs.readFileSync('/path/to/key.pem')
});

const wss = new WebSocket.Server({ server });

server.listen(8080);
```

**Let's Encrypt with Certbot**:
```bash
certbot certonly --standalone -d api.example.com
# Certificates in /etc/letsencrypt/live/api.example.com/
```

### Rate Limiting

Prevent abuse by limiting message frequency:

```javascript
const MESSAGE_LIMIT = 10;
const WINDOW_MS = 1000;

const rateLimits = new WeakMap();

wss.on('connection', (ws) => {
  rateLimits.set(ws, {
    count: 0,
    resetTime: Date.now() + WINDOW_MS
  });

  ws.on('message', (data) => {
    const limit = rateLimits.get(ws);

    if (Date.now() > limit.resetTime) {
      limit.count = 0;
      limit.resetTime = Date.now() + WINDOW_MS;
    }

    limit.count++;

    if (limit.count > MESSAGE_LIMIT) {
      ws.close(1008, 'Rate limit exceeded');
      return;
    }

    // Process message
  });
});
```

**Token Bucket Algorithm**:
```javascript
class TokenBucket {
  constructor(capacity, refillRate) {
    this.capacity = capacity;
    this.tokens = capacity;
    this.refillRate = refillRate; // tokens per second
    this.lastRefill = Date.now();
  }

  consume(tokens = 1) {
    this.refill();

    if (this.tokens >= tokens) {
      this.tokens -= tokens;
      return true;
    }
    return false;
  }

  refill() {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    const tokensToAdd = elapsed * this.refillRate;

    this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
    this.lastRefill = now;
  }
}

const buckets = new WeakMap();

wss.on('connection', (ws) => {
  buckets.set(ws, new TokenBucket(100, 10)); // 100 capacity, 10/sec refill

  ws.on('message', (data) => {
    const bucket = buckets.get(ws);

    if (!bucket.consume()) {
      ws.send(JSON.stringify({ error: 'Rate limit exceeded' }));
      return;
    }

    // Process message
  });
});
```

### Input Validation

Always validate and sanitize incoming messages:

```javascript
ws.on('message', (data, isBinary) => {
  // Size limit
  if (data.length > 1024 * 1024) { // 1MB
    ws.close(1009, 'Message too large');
    return;
  }

  // Type validation
  if (isBinary && !expectBinary) {
    ws.close(1003, 'Unexpected binary data');
    return;
  }

  // JSON parsing
  let msg;
  try {
    msg = JSON.parse(data);
  } catch (e) {
    ws.send(JSON.stringify({ error: 'Invalid JSON' }));
    return;
  }

  // Schema validation
  if (!isValidSchema(msg)) {
    ws.send(JSON.stringify({ error: 'Invalid message format' }));
    return;
  }

  // Sanitize strings (prevent XSS if echoed to browser)
  if (msg.text) {
    msg.text = sanitizeHtml(msg.text);
  }

  // Process validated message
});
```

### DoS Protection

**Connection Limits**:
```javascript
const MAX_CONNECTIONS_PER_IP = 10;
const connections = new Map();

wss.on('connection', (ws, req) => {
  const ip = req.socket.remoteAddress;
  const count = connections.get(ip) || 0;

  if (count >= MAX_CONNECTIONS_PER_IP) {
    ws.close(1008, 'Too many connections');
    return;
  }

  connections.set(ip, count + 1);

  ws.on('close', () => {
    connections.set(ip, connections.get(ip) - 1);
  });
});
```

**Message Size Limits**:
```javascript
const wss = new WebSocket.Server({
  port: 8080,
  maxPayload: 100 * 1024 // 100KB max message size
});
```

**Compression Bomb Protection**:
```javascript
const wss = new WebSocket.Server({
  perMessageDeflate: {
    zlibDeflateOptions: {
      level: 6 // Balanced compression
    },
    zlibInflateOptions: {
      chunkSize: 10 * 1024 // Limit decompressed chunks
    },
    threshold: 1024 // Only compress messages > 1KB
  }
});
```

---

## Scaling Patterns

### Horizontal Scaling Challenges

WebSocket connections are stateful and long-lived, creating scaling challenges:

**Problem**: Client connected to Server A cannot receive messages from Server B.

```
Client 1 ─┬─> Server A ─┐
Client 2 ─┘             │
                        ├─> Load Balancer
Client 3 ─┬─> Server B ─┘
Client 4 ─┘

If Client 1 sends to Client 3, message stuck in Server A.
```

### Sticky Sessions

Route all requests from a client to the same server.

**Load Balancer Configuration** (Nginx):
```nginx
upstream websocket_backend {
  ip_hash; # Route by client IP
  server 10.0.0.1:8080;
  server 10.0.0.2:8080;
  server 10.0.0.3:8080;
}

server {
  listen 80;

  location /ws {
    proxy_pass http://websocket_backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}
```

**HAProxy Configuration**:
```
frontend websocket_front
  bind *:80
  default_backend websocket_back

backend websocket_back
  balance source # IP-based sticky sessions
  server ws1 10.0.0.1:8080 check
  server ws2 10.0.0.2:8080 check
  server ws3 10.0.0.3:8080 check
```

**Limitations**:
- Uneven distribution if clients behind NAT
- Can't rebalance without dropping connections
- Server failure drops all its clients

### Pub/Sub with Redis

Enable cross-server messaging using Redis as a message broker.

**Architecture**:
```
Client 1 ──> Server A ──┐
                        ├──> Redis Pub/Sub ──┬──> Server B ──> Client 3
Client 2 ──> Server B ──┘                    └──> Server C ──> Client 4
```

**Implementation** (Node.js):
```javascript
const Redis = require('ioredis');
const WebSocket = require('ws');

// Create Redis clients (pub and sub must be separate)
const publisher = new Redis();
const subscriber = new Redis();

const wss = new WebSocket.Server({ port: 8080 });

// Subscribe to Redis channel
subscriber.subscribe('chat');

subscriber.on('message', (channel, message) => {
  const msg = JSON.parse(message);

  // Broadcast to all local WebSocket clients
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      // Optional: filter by room/topic
      if (client.room === msg.room) {
        client.send(JSON.stringify(msg));
      }
    }
  });
});

// Handle WebSocket messages
wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const msg = JSON.parse(data);

    // Publish to Redis (reaches all servers)
    publisher.publish('chat', JSON.stringify(msg));
  });
});
```

**Room-Based Routing**:
```javascript
// Client joins room
ws.on('message', (data) => {
  const msg = JSON.parse(data);

  if (msg.type === 'join') {
    ws.room = msg.room;
    subscriber.subscribe(`chat:${msg.room}`);
  } else if (msg.type === 'message') {
    publisher.publish(`chat:${ws.room}`, JSON.stringify(msg));
  }
});

// Targeted broadcast
subscriber.on('message', (channel, message) => {
  const room = channel.split(':')[1];

  wss.clients.forEach((client) => {
    if (client.room === room && client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
});
```

**Presence Tracking**:
```javascript
const PRESENCE_KEY = 'websocket:presence';

wss.on('connection', (ws, req) => {
  const userId = getUserId(req);
  const serverId = process.env.SERVER_ID;

  // Add to presence set
  publisher.sadd(PRESENCE_KEY, JSON.stringify({
    userId,
    serverId,
    connectedAt: Date.now()
  }));

  ws.on('close', () => {
    // Remove from presence set
    publisher.srem(PRESENCE_KEY, JSON.stringify({ userId, serverId }));
  });
});

// Get online users
async function getOnlineUsers() {
  const members = await publisher.smembers(PRESENCE_KEY);
  return members.map(JSON.parse);
}
```

### Redis Streams (Alternative)

More robust than Pub/Sub with message persistence and consumer groups:

```javascript
const Redis = require('ioredis');
const redis = new Redis();

// Producer (WebSocket server receives message)
async function publishMessage(room, message) {
  await redis.xadd(
    `stream:${room}`,
    '*', // Auto-generate ID
    'data', JSON.stringify(message)
  );
}

// Consumer (read messages)
async function consumeMessages(room) {
  let lastId = '0-0';

  while (true) {
    const results = await redis.xread(
      'BLOCK', 1000,
      'STREAMS', `stream:${room}`, lastId
    );

    if (results) {
      const [stream, messages] = results[0];

      messages.forEach(([id, fields]) => {
        const data = fields[1];
        const msg = JSON.parse(data);

        // Broadcast to WebSocket clients
        broadcastToRoom(room, msg);

        lastId = id;
      });
    }
  }
}
```

### Message Queue (RabbitMQ)

For complex routing and guaranteed delivery:

```javascript
const amqp = require('amqplib');

async function setupRabbitMQ() {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();

  // Fanout exchange (broadcast to all)
  await channel.assertExchange('websocket', 'fanout', { durable: false });

  // Create queue for this server
  const { queue } = await channel.assertQueue('', { exclusive: true });
  await channel.bindQueue(queue, 'websocket', '');

  // Consume messages
  channel.consume(queue, (msg) => {
    const data = JSON.parse(msg.content.toString());

    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(data));
      }
    });
  }, { noAck: true });

  // Publish messages
  wss.on('connection', (ws) => {
    ws.on('message', (data) => {
      channel.publish('websocket', '', Buffer.from(data));
    });
  });
}
```

### Database for Persistence

Store messages for offline users:

```javascript
const { Pool } = require('pg');
const pool = new Pool();

// Store message
async function storeMessage(roomId, userId, message) {
  await pool.query(
    'INSERT INTO messages (room_id, user_id, content, created_at) VALUES ($1, $2, $3, NOW())',
    [roomId, userId, message]
  );
}

// Get missed messages
async function getMissedMessages(roomId, since) {
  const result = await pool.query(
    'SELECT * FROM messages WHERE room_id = $1 AND created_at > $2 ORDER BY created_at',
    [roomId, since]
  );
  return result.rows;
}

// On reconnection
ws.on('message', async (data) => {
  const msg = JSON.parse(data);

  if (msg.type === 'join') {
    const missed = await getMissedMessages(msg.room, msg.lastSeen);
    missed.forEach((m) => {
      ws.send(JSON.stringify(m));
    });
  }
});
```

### Serverless WebSocket

**AWS API Gateway WebSockets**:
```javascript
// Lambda handler for connect
exports.connect = async (event) => {
  const connectionId = event.requestContext.connectionId;

  // Store connection in DynamoDB
  await dynamodb.put({
    TableName: 'WebSocketConnections',
    Item: { connectionId, timestamp: Date.now() }
  }).promise();

  return { statusCode: 200 };
};

// Lambda handler for message
exports.message = async (event) => {
  const { connectionId } = event.requestContext;
  const message = JSON.parse(event.body);

  // Get all connections
  const connections = await dynamodb.scan({
    TableName: 'WebSocketConnections'
  }).promise();

  // Send to all
  const apiGateway = new AWS.ApiGatewayManagementApi({
    endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
  });

  await Promise.all(connections.Items.map(async ({ connectionId: targetId }) => {
    try {
      await apiGateway.postToConnection({
        ConnectionId: targetId,
        Data: JSON.stringify(message)
      }).promise();
    } catch (e) {
      if (e.statusCode === 410) {
        // Clean up stale connection
        await dynamodb.delete({
          TableName: 'WebSocketConnections',
          Key: { connectionId: targetId }
        }).promise();
      }
    }
  }));

  return { statusCode: 200 };
};
```

---

## Reconnection Strategies

Network interruptions require robust reconnection logic.

### Exponential Backoff

Avoid overwhelming the server with reconnection attempts:

```javascript
class WebSocketClient {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 10;
    this.reconnectInterval = 1000; // Start at 1 second
    this.maxReconnectInterval = 30000; // Cap at 30 seconds
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('Connected');
      this.reconnectAttempts = 0; // Reset on success
    };

    this.ws.onclose = (event) => {
      console.log('Disconnected:', event.code, event.reason);
      this.reconnect();
    };

    this.ws.onerror = (error) => {
      console.error('Error:', error);
    };
  }

  reconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('Max reconnection attempts reached');
      return;
    }

    this.reconnectAttempts++;

    // Exponential backoff with jitter
    const delay = Math.min(
      this.reconnectInterval * Math.pow(2, this.reconnectAttempts),
      this.maxReconnectInterval
    );

    const jitter = delay * 0.1 * Math.random(); // +/- 10% jitter
    const actualDelay = delay + jitter;

    console.log(`Reconnecting in ${actualDelay}ms (attempt ${this.reconnectAttempts})`);

    setTimeout(() => {
      this.connect();
    }, actualDelay);
  }

  send(data) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(data);
    } else {
      console.warn('Cannot send, not connected');
      // Queue message or handle error
    }
  }
}

const client = new WebSocketClient('wss://api.example.com/ws');
client.connect();
```

### Message Queueing

Buffer messages while disconnected:

```javascript
class RobustWebSocket {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.messageQueue = [];
    this.maxQueueSize = 100;
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('Connected, flushing queue');
      this.flushQueue();
    };

    this.ws.onclose = () => {
      this.reconnect();
    };
  }

  send(data) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(data);
    } else {
      // Queue message
      if (this.messageQueue.length < this.maxQueueSize) {
        this.messageQueue.push(data);
      } else {
        console.warn('Message queue full, dropping oldest');
        this.messageQueue.shift();
        this.messageQueue.push(data);
      }
    }
  }

  flushQueue() {
    while (this.messageQueue.length > 0 && this.ws.readyState === WebSocket.OPEN) {
      const message = this.messageQueue.shift();
      this.ws.send(message);
    }
  }

  reconnect() {
    // Exponential backoff logic from previous example
  }
}
```

### State Synchronization

Resync state after reconnection:

```javascript
class StatefulWebSocket {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.lastMessageId = 0;
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      // Request missed messages
      this.ws.send(JSON.stringify({
        type: 'sync',
        lastMessageId: this.lastMessageId
      }));
    };

    this.ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);

      if (msg.id > this.lastMessageId) {
        this.lastMessageId = msg.id;
        this.handleMessage(msg);
      } else {
        // Duplicate or old message, ignore
      }
    };
  }

  handleMessage(msg) {
    // Process message
    console.log('Message:', msg);
  }
}
```

### Heartbeat Detection

Detect connection death faster than TCP keepalive:

```javascript
class HeartbeatWebSocket {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.heartbeatInterval = null;
    this.heartbeatTimeout = null;
    this.HEARTBEAT_INTERVAL = 30000; // 30 seconds
    this.HEARTBEAT_TIMEOUT = 5000; // 5 seconds
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      this.startHeartbeat();
    };

    this.ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);

      if (msg.type === 'pong') {
        this.resetHeartbeat();
      } else {
        this.handleMessage(msg);
      }
    };

    this.ws.onclose = () => {
      this.stopHeartbeat();
      this.reconnect();
    };
  }

  startHeartbeat() {
    this.heartbeatInterval = setInterval(() => {
      this.ping();
    }, this.HEARTBEAT_INTERVAL);
  }

  stopHeartbeat() {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
      this.heartbeatInterval = null;
    }
    if (this.heartbeatTimeout) {
      clearTimeout(this.heartbeatTimeout);
      this.heartbeatTimeout = null;
    }
  }

  ping() {
    this.ws.send(JSON.stringify({ type: 'ping' }));

    // Expect pong within timeout
    this.heartbeatTimeout = setTimeout(() => {
      console.warn('Heartbeat timeout, reconnecting');
      this.ws.close();
    }, this.HEARTBEAT_TIMEOUT);
  }

  resetHeartbeat() {
    if (this.heartbeatTimeout) {
      clearTimeout(this.heartbeatTimeout);
      this.heartbeatTimeout = null;
    }
  }

  reconnect() {
    // Exponential backoff logic
  }
}
```

### Connection State Machine

Robust state management:

```javascript
const States = {
  DISCONNECTED: 'disconnected',
  CONNECTING: 'connecting',
  CONNECTED: 'connected',
  RECONNECTING: 'reconnecting'
};

class StateMachineWebSocket {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.state = States.DISCONNECTED;
    this.listeners = {};
  }

  connect() {
    if (this.state !== States.DISCONNECTED) {
      console.warn('Already connecting or connected');
      return;
    }

    this.setState(States.CONNECTING);
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      this.setState(States.CONNECTED);
    };

    this.ws.onclose = () => {
      if (this.state === States.CONNECTED) {
        this.setState(States.RECONNECTING);
        this.reconnect();
      } else {
        this.setState(States.DISCONNECTED);
      }
    };

    this.ws.onerror = (error) => {
      this.emit('error', error);
    };
  }

  setState(newState) {
    const oldState = this.state;
    this.state = newState;
    this.emit('stateChange', { from: oldState, to: newState });
  }

  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }

  emit(event, data) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(cb => cb(data));
    }
  }

  reconnect() {
    // Exponential backoff
    setTimeout(() => {
      this.state = States.DISCONNECTED;
      this.connect();
    }, 1000);
  }
}

// Usage
const ws = new StateMachineWebSocket('wss://api.example.com/ws');

ws.on('stateChange', ({ from, to }) => {
  console.log(`State: ${from} -> ${to}`);

  if (to === States.CONNECTED) {
    // Update UI to show connected
  } else if (to === States.RECONNECTING) {
    // Show reconnecting indicator
  }
});

ws.connect();
```

---

## Server Implementations

### Node.js (ws library)

**Installation**:
```bash
npm install ws
```

**Basic Server**:
```javascript
const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws, req) => {
  console.log('Client connected:', req.socket.remoteAddress);

  ws.on('message', (data, isBinary) => {
    console.log('Received:', data.toString());

    // Echo back
    ws.send(data, { binary: isBinary });
  });

  ws.on('close', (code, reason) => {
    console.log('Client disconnected:', code, reason.toString());
  });

  ws.on('error', (error) => {
    console.error('WebSocket error:', error);
  });

  ws.send('Welcome!');
});

console.log('WebSocket server running on ws://localhost:8080');
```

**Broadcast to All Clients**:
```javascript
function broadcast(data) {
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(data);
    }
  });
}

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    broadcast(data); // Send to all connected clients
  });
});
```

**Room-Based Broadcasting**:
```javascript
const rooms = new Map();

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const msg = JSON.parse(data);

    if (msg.type === 'join') {
      if (!rooms.has(msg.room)) {
        rooms.set(msg.room, new Set());
      }
      rooms.get(msg.room).add(ws);
      ws.room = msg.room;
    } else if (msg.type === 'message') {
      const room = rooms.get(ws.room);
      if (room) {
        room.forEach((client) => {
          if (client.readyState === WebSocket.OPEN) {
            client.send(JSON.stringify(msg));
          }
        });
      }
    }
  });

  ws.on('close', () => {
    if (ws.room && rooms.has(ws.room)) {
      rooms.get(ws.room).delete(ws);
    }
  });
});
```

**With Express**:
```javascript
const express = require('express');
const { createServer } = require('http');
const WebSocket = require('ws');

const app = express();
const server = createServer(app);
const wss = new WebSocket.Server({ server });

app.get('/', (req, res) => {
  res.send('WebSocket server running');
});

wss.on('connection', (ws) => {
  ws.send('Connected to WebSocket server');
});

server.listen(8080, () => {
  console.log('Server running on http://localhost:8080');
});
```

**Compression (permessage-deflate)**:
```javascript
const wss = new WebSocket.Server({
  port: 8080,
  perMessageDeflate: {
    zlibDeflateOptions: {
      chunkSize: 1024,
      memLevel: 7,
      level: 3
    },
    zlibInflateOptions: {
      chunkSize: 10 * 1024
    },
    clientNoContextTakeover: true,
    serverNoContextTakeover: true,
    serverMaxWindowBits: 10,
    concurrencyLimit: 10,
    threshold: 1024 // Only compress messages > 1KB
  }
});
```

### Python (websockets library)

**Installation**:
```bash
pip install websockets
```

**Basic Server** (async):
```python
import asyncio
import websockets

async def handler(websocket, path):
    print(f"Client connected: {websocket.remote_address}")

    try:
        async for message in websocket:
            print(f"Received: {message}")

            # Echo back
            await websocket.send(message)
    except websockets.exceptions.ConnectionClosed:
        print("Client disconnected")

async def main():
    async with websockets.serve(handler, "localhost", 8080):
        print("WebSocket server running on ws://localhost:8080")
        await asyncio.Future()  # Run forever

asyncio.run(main())
```

**Broadcast to All Clients**:
```python
import asyncio
import websockets

connected = set()

async def handler(websocket, path):
    connected.add(websocket)
    try:
        async for message in websocket:
            # Broadcast to all clients
            websockets.broadcast(connected, message)
    finally:
        connected.remove(websocket)

async def main():
    async with websockets.serve(handler, "localhost", 8080):
        await asyncio.Future()

asyncio.run(main())
```

**JSON Messages**:
```python
import asyncio
import websockets
import json

async def handler(websocket, path):
    async for message in websocket:
        data = json.loads(message)

        # Process
        response = {
            "type": "response",
            "echo": data
        }

        await websocket.send(json.dumps(response))

async def main():
    async with websockets.serve(handler, "localhost", 8080):
        await asyncio.Future()

asyncio.run(main())
```

**With Authentication**:
```python
import asyncio
import websockets
import jwt

async def handler(websocket, path):
    # Read first message as auth token
    try:
        token = await asyncio.wait_for(websocket.recv(), timeout=5.0)
        payload = jwt.decode(token, "secret", algorithms=["HS256"])
        user_id = payload["user_id"]
    except (asyncio.TimeoutError, jwt.InvalidTokenError):
        await websocket.close(1008, "Authentication failed")
        return

    print(f"User {user_id} authenticated")

    # Continue with authenticated connection
    async for message in websocket:
        await websocket.send(f"User {user_id}: {message}")

async def main():
    async with websockets.serve(handler, "localhost", 8080):
        await asyncio.Future()

asyncio.run(main())
```

### Go (gorilla/websocket)

**Installation**:
```bash
go get github.com/gorilla/websocket
```

**Basic Server**:
```go
package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true // Allow all origins (configure for production)
    },
}

func handler(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()

    fmt.Println("Client connected:", conn.RemoteAddr())

    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read error:", err)
            break
        }

        fmt.Printf("Received: %s\n", message)

        // Echo back
        err = conn.WriteMessage(messageType, message)
        if err != nil {
            log.Println("Write error:", err)
            break
        }
    }
}

func main() {
    http.HandleFunc("/ws", handler)

    fmt.Println("WebSocket server running on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}
```

**Broadcast Hub**:
```go
package main

import (
    "github.com/gorilla/websocket"
)

type Hub struct {
    clients    map[*websocket.Conn]bool
    broadcast  chan []byte
    register   chan *websocket.Conn
    unregister chan *websocket.Conn
}

func newHub() *Hub {
    return &Hub{
        clients:    make(map[*websocket.Conn]bool),
        broadcast:  make(chan []byte),
        register:   make(chan *websocket.Conn),
        unregister: make(chan *websocket.Conn),
    }
}

func (h *Hub) run() {
    for {
        select {
        case conn := <-h.register:
            h.clients[conn] = true
        case conn := <-h.unregister:
            if _, ok := h.clients[conn]; ok {
                delete(h.clients, conn)
                conn.Close()
            }
        case message := <-h.broadcast:
            for conn := range h.clients {
                err := conn.WriteMessage(websocket.TextMessage, message)
                if err != nil {
                    h.unregister <- conn
                }
            }
        }
    }
}

var hub = newHub()

func handler(w http.ResponseWriter, r *http.Request) {
    conn, _ := upgrader.Upgrade(w, r, nil)
    hub.register <- conn

    defer func() {
        hub.unregister <- conn
    }()

    for {
        _, message, err := conn.ReadMessage()
        if err != nil {
            break
        }
        hub.broadcast <- message
    }
}

func main() {
    go hub.run()

    http.HandleFunc("/ws", handler)
    http.ListenAndServe(":8080", nil)
}
```

---

## Client Implementations

### Browser (Native WebSocket API)

**Basic Connection**:
```javascript
const ws = new WebSocket('wss://api.example.com/ws');

ws.onopen = (event) => {
  console.log('Connected');
  ws.send('Hello Server');
};

ws.onmessage = (event) => {
  console.log('Received:', event.data);

  // Parse JSON if needed
  const data = JSON.parse(event.data);
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = (event) => {
  console.log('Disconnected:', event.code, event.reason);
};
```

**Send Binary Data**:
```javascript
// ArrayBuffer
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint32(0, 42);
ws.send(buffer);

// Blob
const blob = new Blob(['binary data']);
ws.send(blob);
```

**Receive Binary Data**:
```javascript
ws.binaryType = 'arraybuffer'; // or 'blob'

ws.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    const view = new DataView(event.data);
    const value = view.getUint32(0);
    console.log('Binary:', value);
  } else {
    console.log('Text:', event.data);
  }
};
```

### Node.js Client (ws library)

```javascript
const WebSocket = require('ws');

const ws = new WebSocket('wss://api.example.com/ws');

ws.on('open', () => {
  console.log('Connected');
  ws.send('Hello');
});

ws.on('message', (data, isBinary) => {
  console.log('Received:', data.toString());
});

ws.on('close', (code, reason) => {
  console.log('Closed:', code, reason.toString());
});

ws.on('error', (error) => {
  console.error('Error:', error);
});
```

### Python Client (websockets)

```python
import asyncio
import websockets

async def client():
    async with websockets.connect('wss://api.example.com/ws') as ws:
        await ws.send('Hello')

        async for message in ws:
            print(f"Received: {message}")

asyncio.run(client())
```

### React Hook (useWebSocket)

```typescript
import { useEffect, useRef, useState } from 'react';

function useWebSocket(url: string) {
  const [isConnected, setIsConnected] = useState(false);
  const [lastMessage, setLastMessage] = useState<string | null>(null);
  const ws = useRef<WebSocket | null>(null);

  useEffect(() => {
    ws.current = new WebSocket(url);

    ws.current.onopen = () => {
      setIsConnected(true);
    };

    ws.current.onmessage = (event) => {
      setLastMessage(event.data);
    };

    ws.current.onclose = () => {
      setIsConnected(false);
    };

    return () => {
      ws.current?.close();
    };
  }, [url]);

  const send = (data: string) => {
    if (ws.current?.readyState === WebSocket.OPEN) {
      ws.current.send(data);
    }
  };

  return { isConnected, lastMessage, send };
}

// Usage
function App() {
  const { isConnected, lastMessage, send } = useWebSocket('wss://api.example.com/ws');

  return (
    <div>
      <p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
      <p>Last message: {lastMessage}</p>
      <button onClick={() => send('Hello')}>Send</button>
    </div>
  );
}
```

---

## Testing Strategies

### Unit Testing

**Test Message Handling**:
```javascript
const WebSocket = require('ws');

describe('WebSocket Handler', () => {
  let wss, ws;

  beforeAll((done) => {
    wss = new WebSocket.Server({ port: 8080 });
    wss.on('connection', (socket) => {
      socket.on('message', (data) => {
        const msg = JSON.parse(data);
        socket.send(JSON.stringify({ echo: msg }));
      });
    });
    done();
  });

  afterAll(() => {
    wss.close();
  });

  test('echoes message', (done) => {
    ws = new WebSocket('ws://localhost:8080');

    ws.on('open', () => {
      ws.send(JSON.stringify({ test: 'data' }));
    });

    ws.on('message', (data) => {
      const msg = JSON.parse(data);
      expect(msg.echo.test).toBe('data');
      ws.close();
      done();
    });
  });
});
```

### Integration Testing

**Test Full Flow**:
```javascript
describe('Chat Integration', () => {
  let server, client1, client2;

  beforeAll(async () => {
    server = new WebSocket.Server({ port: 8080 });
    // Setup broadcast logic

    client1 = new WebSocket('ws://localhost:8080');
    client2 = new WebSocket('ws://localhost:8080');

    await Promise.all([
      new Promise((resolve) => client1.on('open', resolve)),
      new Promise((resolve) => client2.on('open', resolve))
    ]);
  });

  test('broadcasts message to all clients', (done) => {
    client2.on('message', (data) => {
      expect(data.toString()).toBe('Hello');
      done();
    });

    client1.send('Hello');
  });

  afterAll(() => {
    client1.close();
    client2.close();
    server.close();
  });
});
```

### Load Testing

**Artillery**:
```yaml
# artillery.yml
config:
  target: "wss://api.example.com"
  phases:
    - duration: 60
      arrivalRate: 10
      name: "Warm up"
    - duration: 120
      arrivalRate: 50
      name: "Sustained load"
  engines:
    ws:
      timeout: 60

scenarios:
  - engine: ws
    flow:
      - connect:
          target: "/ws"
      - send: '{"type": "join", "room": "test"}'
      - think: 1
      - loop:
          - send: '{"type": "message", "text": "Hello"}'
          - think: 2
        count: 10
```

```bash
artillery run artillery.yml
```

**k6**:
```javascript
import ws from 'k6/ws';
import { check } from 'k6';

export let options = {
  vus: 100,
  duration: '30s',
};

export default function () {
  const url = 'wss://api.example.com/ws';

  const res = ws.connect(url, {}, function (socket) {
    socket.on('open', () => socket.send('Hello'));

    socket.on('message', (data) => {
      console.log('Received:', data);
    });

    socket.setTimeout(() => {
      socket.close();
    }, 10000);
  });

  check(res, { 'status is 101': (r) => r && r.status === 101 });
}
```

---

## Common Patterns

### Chat Application

**Server** (Node.js):
```javascript
const rooms = new Map();

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const msg = JSON.parse(data);

    switch (msg.type) {
      case 'join':
        if (!rooms.has(msg.room)) {
          rooms.set(msg.room, new Set());
        }
        rooms.get(msg.room).add(ws);
        ws.room = msg.room;
        ws.username = msg.username;
        break;

      case 'message':
        const room = rooms.get(ws.room);
        if (room) {
          const payload = JSON.stringify({
            type: 'message',
            username: ws.username,
            text: msg.text,
            timestamp: Date.now()
          });

          room.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
              client.send(payload);
            }
          });
        }
        break;
    }
  });

  ws.on('close', () => {
    if (ws.room && rooms.has(ws.room)) {
      rooms.get(ws.room).delete(ws);
    }
  });
});
```

**Client** (React):
```typescript
function Chat() {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');
  const ws = useRef<WebSocket>();

  useEffect(() => {
    ws.current = new WebSocket('wss://api.example.com/ws');

    ws.current.onopen = () => {
      ws.current.send(JSON.stringify({
        type: 'join',
        room: 'general',
        username: 'Alice'
      }));
    };

    ws.current.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      setMessages((prev) => [...prev, msg]);
    };

    return () => ws.current?.close();
  }, []);

  const sendMessage = () => {
    ws.current?.send(JSON.stringify({
      type: 'message',
      text: input
    }));
    setInput('');
  };

  return (
    <div>
      {messages.map((msg, i) => (
        <div key={i}>{msg.username}: {msg.text}</div>
      ))}
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={sendMessage}>Send</button>
    </div>
  );
}
```

### Real-Time Dashboard

**Server** (push updates):
```javascript
const clients = new Set();

wss.on('connection', (ws) => {
  clients.add(ws);

  ws.on('close', () => {
    clients.remove(ws);
  });
});

// Push data updates
setInterval(() => {
  const data = {
    type: 'metrics',
    cpu: Math.random() * 100,
    memory: Math.random() * 100,
    timestamp: Date.now()
  };

  clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(JSON.stringify(data));
    }
  });
}, 1000);
```

**Client** (React with Chart):
```typescript
function Dashboard() {
  const [metrics, setMetrics] = useState([]);

  useEffect(() => {
    const ws = new WebSocket('wss://api.example.com/ws');

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setMetrics((prev) => [...prev.slice(-60), data]); // Keep last 60
    };

    return () => ws.close();
  }, []);

  return (
    <LineChart data={metrics}>
      <Line dataKey="cpu" stroke="#8884d8" />
      <Line dataKey="memory" stroke="#82ca9d" />
    </LineChart>
  );
}
```

### Multiplayer Game (State Sync)

**Server**:
```javascript
const gameState = {
  players: new Map()
};

wss.on('connection', (ws) => {
  const playerId = generateId();

  gameState.players.set(playerId, {
    id: playerId,
    x: 0,
    y: 0
  });

  // Send full state to new player
  ws.send(JSON.stringify({
    type: 'init',
    playerId,
    state: Array.from(gameState.players.values())
  }));

  // Broadcast new player to others
  broadcast({
    type: 'player_joined',
    player: gameState.players.get(playerId)
  });

  ws.on('message', (data) => {
    const msg = JSON.parse(data);

    if (msg.type === 'move') {
      const player = gameState.players.get(playerId);
      player.x = msg.x;
      player.y = msg.y;

      // Broadcast position update
      broadcast({
        type: 'player_moved',
        playerId,
        x: msg.x,
        y: msg.y
      });
    }
  });

  ws.on('close', () => {
    gameState.players.delete(playerId);
    broadcast({
      type: 'player_left',
      playerId
    });
  });
});
```

---

## Comparison with Alternatives

### Server-Sent Events (SSE)

**SSE Characteristics**:
- Unidirectional (server → client only)
- HTTP-based (simpler than WebSocket)
- Automatic reconnection
- Text-only (no binary)
- EventSource API in browsers

**When to Use SSE**:
- One-way server push (notifications, live feeds)
- Simpler infrastructure requirements
- No binary data needed
- Want automatic reconnection

**When to Use WebSocket**:
- Bidirectional communication needed
- Low latency critical
- Binary data
- Custom protocols

**SSE Example**:
```javascript
// Server (Express)
app.get('/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
  }, 1000);

  req.on('close', () => {
    clearInterval(interval);
  });
});

// Client
const eventSource = new EventSource('/events');

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log(data);
};
```

### Long Polling

**Characteristics**:
- Client repeatedly requests updates
- Server holds request until data available
- High overhead (new request each time)

**When to Use Long Polling**:
- Legacy browser support
- Restrictive firewall/proxy
- Very infrequent updates

**When to Use WebSocket**:
- Frequent updates
- Modern browsers
- Real-time requirements

### HTTP/2 Server Push

**Characteristics**:
- Server can push resources proactively
- Still request/response model
- Limited browser support

**WebSocket Advantages**:
- True bidirectional
- Lower overhead
- Better browser support
- Persistent connection

---

## Production Best Practices

### Monitoring

**Key Metrics**:
- Active connections count
- Message throughput (messages/sec)
- Message latency (time from send to receive)
- Connection duration
- Reconnection rate
- Error rate
- Memory usage per connection

**Prometheus Metrics**:
```javascript
const prometheus = require('prom-client');

const activeConnections = new prometheus.Gauge({
  name: 'websocket_connections_active',
  help: 'Number of active WebSocket connections'
});

const messagesTotal = new prometheus.Counter({
  name: 'websocket_messages_total',
  help: 'Total number of WebSocket messages',
  labelNames: ['direction', 'type']
});

const messageLatency = new prometheus.Histogram({
  name: 'websocket_message_latency_seconds',
  help: 'WebSocket message latency',
  buckets: [0.001, 0.01, 0.1, 0.5, 1]
});

wss.on('connection', (ws) => {
  activeConnections.inc();

  ws.on('message', (data) => {
    messagesTotal.inc({ direction: 'inbound', type: 'text' });

    const start = Date.now();
    // Process message
    const duration = (Date.now() - start) / 1000;
    messageLatency.observe(duration);
  });

  ws.on('close', () => {
    activeConnections.dec();
  });
});
```

### Logging

**Structured Logging**:
```javascript
const winston = require('winston');

const logger = winston.createLogger({
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'websocket.log' })
  ]
});

wss.on('connection', (ws, req) => {
  const connId = generateId();

  logger.info('connection', {
    connId,
    ip: req.socket.remoteAddress,
    origin: req.headers.origin
  });

  ws.on('message', (data) => {
    logger.debug('message', {
      connId,
      size: data.length,
      type: typeof data
    });
  });

  ws.on('close', (code, reason) => {
    logger.info('disconnect', {
      connId,
      code,
      reason: reason.toString()
    });
  });

  ws.on('error', (error) => {
    logger.error('error', {
      connId,
      error: error.message,
      stack: error.stack
    });
  });
});
```

### Resource Limits

**Per-Connection Limits**:
```javascript
const MAX_MESSAGE_SIZE = 100 * 1024; // 100KB
const MAX_MESSAGES_PER_MINUTE = 60;

const wss = new WebSocket.Server({
  port: 8080,
  maxPayload: MAX_MESSAGE_SIZE
});

const rateLimits = new WeakMap();

wss.on('connection', (ws) => {
  rateLimits.set(ws, {
    count: 0,
    resetAt: Date.now() + 60000
  });

  ws.on('message', (data) => {
    const limit = rateLimits.get(ws);

    if (Date.now() > limit.resetAt) {
      limit.count = 0;
      limit.resetAt = Date.now() + 60000;
    }

    if (++limit.count > MAX_MESSAGES_PER_MINUTE) {
      ws.close(1008, 'Rate limit exceeded');
      return;
    }
  });
});
```

### Graceful Shutdown

```javascript
process.on('SIGTERM', () => {
  console.log('SIGTERM received, closing connections');

  wss.clients.forEach((ws) => {
    ws.close(1001, 'Server shutting down');
  });

  const timeout = setTimeout(() => {
    console.log('Force closing remaining connections');
    wss.close(() => {
      process.exit(0);
    });
  }, 30000);

  wss.close(() => {
    clearTimeout(timeout);
    process.exit(0);
  });
});
```

---

## Troubleshooting

### Connection Fails

**Check**:
1. Correct URL scheme (ws:// vs wss://)
2. Server is running and accessible
3. Firewall allows WebSocket connections
4. Load balancer configured for WebSocket upgrade
5. Certificate valid (for wss://)

**Debug**:
```javascript
ws.onerror = (error) => {
  console.error('Connection error:', error);
  // Check: incorrect URL, network issues, server down
};

ws.onclose = (event) => {
  console.log('Close code:', event.code);
  console.log('Close reason:', event.reason);
  // 1006 = abnormal closure (network issue)
  // 1011 = server error
};
```

### Messages Not Received

**Check**:
1. Connection state (OPEN)
2. Message size within limits
3. Proper masking (client→server)
4. Valid UTF-8 (text messages)
5. Server is broadcasting correctly

**Debug**:
```javascript
console.log('WebSocket state:', ws.readyState);
// 0 = CONNECTING, 1 = OPEN, 2 = CLOSING, 3 = CLOSED

if (ws.readyState === WebSocket.OPEN) {
  ws.send('test');
} else {
  console.warn('Cannot send, connection not open');
}
```

### High Latency

**Causes**:
- Network congestion
- Server overload
- Message queue backlog
- Slow message processing

**Solutions**:
```javascript
// Measure latency
ws.send(JSON.stringify({ type: 'ping', sent: Date.now() }));

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === 'pong') {
    const latency = Date.now() - msg.sent;
    console.log('Latency:', latency, 'ms');
  }
};

// Server-side processing time
const start = Date.now();
// ... process message ...
const duration = Date.now() - start;
if (duration > 100) {
  console.warn('Slow processing:', duration, 'ms');
}
```

### Memory Leaks

**Common Causes**:
- Not removing event listeners
- Storing references to closed connections
- Unbounded message buffers

**Prevention**:
```javascript
const clients = new Set();

wss.on('connection', (ws) => {
  clients.add(ws);

  ws.on('close', () => {
    clients.delete(ws); // Clean up reference
  });
});

// Use WeakMap for connection metadata
const metadata = new WeakMap();

wss.on('connection', (ws) => {
  metadata.set(ws, { /* data */ }); // Auto-cleaned when ws GC'd
});
```

---

## Performance Optimization

### Compression

Enable permessage-deflate for text messages:

```javascript
const wss = new WebSocket.Server({
  perMessageDeflate: {
    threshold: 1024 // Only compress > 1KB
  }
});
```

**Trade-offs**:
- CPU usage vs bandwidth
- Latency increase (compression time)
- Memory usage (compression contexts)

### Binary Protocols

Use binary formats for structured data:

**MessagePack**:
```javascript
const msgpack = require('msgpack-lite');

// Send
const data = { user: 'Alice', count: 42 };
const encoded = msgpack.encode(data);
ws.send(encoded, { binary: true });

// Receive
ws.on('message', (data, isBinary) => {
  if (isBinary) {
    const decoded = msgpack.decode(data);
  }
});
```

**Protocol Buffers**:
```protobuf
message ChatMessage {
  string username = 1;
  string text = 2;
  int64 timestamp = 3;
}
```

```javascript
const protobuf = require('protobufjs');

const root = protobuf.loadSync('chat.proto');
const ChatMessage = root.lookupType('ChatMessage');

// Send
const message = ChatMessage.create({
  username: 'Alice',
  text: 'Hello',
  timestamp: Date.now()
});
const buffer = ChatMessage.encode(message).finish();
ws.send(buffer, { binary: true });
```

### Batching

Reduce overhead by batching multiple messages:

```javascript
const batch = [];
const BATCH_SIZE = 10;
const BATCH_INTERVAL = 100; // ms

function queueMessage(msg) {
  batch.push(msg);

  if (batch.length >= BATCH_SIZE) {
    flushBatch();
  }
}

function flushBatch() {
  if (batch.length === 0) return;

  ws.send(JSON.stringify(batch));
  batch.length = 0;
}

setInterval(flushBatch, BATCH_INTERVAL);
```

### Connection Pooling (Client)

Reuse connections for multiple logical channels:

```javascript
class WebSocketPool {
  constructor(url, size) {
    this.connections = [];
    for (let i = 0; i < size; i++) {
      this.connections.push(new WebSocket(url));
    }
    this.index = 0;
  }

  getConnection() {
    const conn = this.connections[this.index];
    this.index = (this.index + 1) % this.connections.length;
    return conn;
  }

  send(data) {
    const conn = this.getConnection();
    if (conn.readyState === WebSocket.OPEN) {
      conn.send(data);
    }
  }
}

const pool = new WebSocketPool('wss://api.example.com/ws', 5);
pool.send('message 1');
pool.send('message 2');
```

---

**End of WebSocket Reference**

This comprehensive reference covers the WebSocket protocol from fundamentals to production deployment. For hands-on examples, see the `examples/` directory. For testing and analysis tools, see the `scripts/` directory.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.