Communication Mechanisms
Detailed Explanation of the WebSocket Handshake Process (HTTP Upgrade Header)
WebSocket Handshake Process:
- The client initiates an HTTP Upgrade request.
- The server responds with a 101 Switching Protocols status.
- A full-duplex communication channel is established.
Detailed Handshake Flow:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.comServer Response:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Handshake Source Code Implementation (Node.js):
const http = require('http');
const crypto = require('crypto');
const server = http.createServer((req, res) => {
if (req.headers.upgrade && req.headers.upgrade.toLowerCase() === 'websocket') {
// Validate WebSocket handshake request
if (req.headers['sec-websocket-key']) {
// Calculate Sec-WebSocket-Accept
const acceptKey = calculateAcceptKey(req.headers['sec-websocket-key']);
// Send 101 response
res.writeHead(101, {
'Upgrade': 'websocket',
'Connection': 'Upgrade',
'Sec-WebSocket-Accept': acceptKey
});
res.end();
// Handshake successful, upgrade to WebSocket connection
handleWebSocketConnection(req.socket);
} else {
res.writeHead(400);
res.end('Missing Sec-WebSocket-Key');
}
} else {
res.writeHead(400);
res.end('Not a WebSocket request');
}
});
function calculateAcceptKey(key) {
const magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const sha1 = crypto.createHash('sha1').update(key + magic).digest('base64');
return sha1;
}
function handleWebSocketConnection(socket) {
// WebSocket connection handling logic
console.log('WebSocket connection established');
}
server.listen(8080, () => {
console.log('WebSocket server running at ws://localhost:8080');
});WebSocket Frame Parsing and Encapsulation
WebSocket 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 ... |
+---------------------------------------------------------------+Frame Types (opcode):
- 0x0: Continuation frame
- 0x1: Text frame
- 0x2: Binary frame
- 0x8: Connection close
- 0x9: Ping
- 0xA: Pong
Frame Parsing Source Code Implementation:
class WebSocketFrame {
constructor(buffer) {
this.buffer = buffer;
this.fin = (buffer[0] & 0x80) !== 0;
this.rsv1 = (buffer[0] & 0x40) !== 0;
this.rsv2 = (buffer[0] & 0x20) !== 0;
this.rsv3 = (buffer[0] & 0x10) !== 0;
this.opcode = buffer[0] & 0x0F;
this.mask = (buffer[1] & 0x80) !== 0;
this.payloadLength = buffer[1] & 0x7F;
this.maskingKey = null;
this.payloadData = null;
this.parse();
}
parse() {
let offset = 2;
// Handle extended length
if (this.payloadLength === 126) {
this.payloadLength = (buffer[offset] << 8) | buffer[offset + 1];
offset += 2;
} else if (this.payloadLength === 127) {
this.payloadLength = (buffer[offset] << 56) | (buffer[offset + 1] << 48) |
(buffer[offset + 2] << 40) | (buffer[offset + 3] << 32) |
(buffer[offset + 4] << 24) | (buffer[offset + 5] << 16) |
(buffer[offset + 6] << 8) | buffer[offset + 7];
offset += 8;
}
// Handle missing data
if (this.mask) {
this.maskingKey = buffer.slice(offset, offset + 4);
offset += 4;
}
// Extract payload data
this.payloadData = buffer.slice(offset, offset + this.payloadLength);
// Unmask payload if masked
if (this.mask && this.maskedData) {
this.payloadData = this.unmaskPayload(this.payload, this.maskedData);
}
}
unmaskPayload(payload, maskingKey) {
const unmasked = new Uint8Array(payload.length);
for (let i = 0; i < payload.length; i++) {
unmasked[i] = payload[i] ^ maskingKey[i % 4];
}
return unmasked;
}
}Message Boundary Handling (Message Fragmentation and Reassembly)
Message Fragmentation Mechanism:
- Fragmentation Types:
- Intermediate fragment (Opcode=0x0)
- – Start fragment (Opcode=0x1 or 0x2)
- – End fragment (FIN=1)
Fragmentation Reassembly Process:
- Receive start fragment, initialize buffer
- Receive intermediate fragments, append to buffer
- Receive end fragment, complete reassembly
Fragmentation Reassembly Source Code Implementation:
class WebSocketMessageAssembler {
constructor() {
this.fragments = new Map(); // clientId -> { opcode, fragments }
this.maxFragmentSize = 16 * 1024 * 1024; // 16MB maximum fragment size
}
processFrame(frame, clientId) {
if (!this.fragments.has(clientId)) {
this.fragments.set(clientId, {
opcode: null,
fragments: []
});
}
const assembler = this.fragments.get(clientId);
if (frame.fin) {
// End fragment, complete reassembly
if (assembler.opcode === null) {
// Single frame message
return frame.payloadData;
} else {
// Fragmented message
assembler.fragments.push(frame.payloadData);
const completeMessage = this.concatFragments(assembler.fragments);
this.fragments.delete(clientId);
return completeMessage;
}
} else {
// Intermediate or start fragment
if (assembler.opcode === null) {
// Start fragment
assembler.opcode = frame.opcode;
assembler.fragments.push(frame.payloadData);
} else {
// Intermediate fragment
assembler.fragments.push(frame.payloadData);
// Check if fragment size exceeds limit
const totalSize = assembler.fragments.reduce((sum, frag) => sum + frag.length, 0);
if (totalSize > this.maxFragmentSize) {
this.fragments.delete(clientId);
throw new Error('Message fragment size exceeds limit');
}
}
return null; // Message incomplete
}
}
concatFragments(fragments) {
let totalLength = fragments.reduce((sum, frag) => sum + frag.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const frag of fragments) {
result.set(frag, offset);
offset += frag.length;
}
return result;
}
}Connection State Management (Timeout, Reconnection)
Connection State Management Strategies:
- Heartbeat mechanism: Periodic Ping/Pong to check connection health
- Timeout settings:
- Handshake timeout
- Message response timeout
- Idle connection timeout
- Reconnection strategies:
- Exponential backoff algorithm
- Maximum retry limit
Heartbeat Detection Implementation:
class WebSocketHeartbeat {
constructor(ws, options = {}) {
this.ws = ws;
this.interval = options.interval || 30000; // 30 seconds
this.timeout = options.timeout || 5000; // 5 seconds
this.pingTimeout = null;
this.pongTimeout = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.start();
}
start() {
this.schedulePing();
}
stop() {
clearTimeout(this.pingTimeout);
clearTimeout(this.pongTimeout);
}
schedulePing() {
this.pingTimeout = setTimeout(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.ping();
this.schedulePongTimeout();
}
}, this.interval);
}
schedulePongTimeout() {
this.pongTimeout = setTimeout(() => {
console.error('Pong response timeout, closing connection');
this.ws.close(1001, 'Pong timeout');
}, this.timeout);
}
handlePong() {
clearTimeout(this.pongTimeout);
this.schedulePing();
}
handleOpen() {
this.reconnectAttempts = 0;
this.schedulePing();
}
handleClose() {
this.stop();
if (this.reconnectAttempts < this.maxReconnectAttempts) {
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
this.reconnectAttempts++;
console.log(`Will attempt to reconnect in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
setTimeout(() => {
// Implement reconnection logic
this.reconnect();
}, delay);
} else {
console.log('Maximum reconnect attempts reached');
}
}
reconnect() {
// Implement reconnection logic
// Typically requires creating a new WebSocket instance
}
}
// Usage example
const ws = new WebSocket('ws://example.com/socket');
const heartbeat = new WebSocketHeartbeat(ws, {
interval: 30000,
timeout: 5000
});
ws.onopen = () => heartbeat.handleOpen();
ws.onclose = () => heartbeat.handleClose();
ws.onpong = () => heartbeat.handlePong();Multi-Client Communication Modes (Broadcast, Unicast, Multicast)
Communication Modes Implementation:
- Unicast: Point-to-point message delivery
- Broadcast: Send message to all clients
- Multicast: Send message to specific group of clients
Multi-Client Management Implementation:
class WebSocketManager {
constructor() {
this.clients = new Map(); // clientId -> WebSocket
this.clientGroups = new Map(); // groupId -> Set<clientId>
}
addClient(clientId, ws) {
this.clients.set(clientId, ws);
}
removeClient(clientId) {
this.clients.delete(clientId);
// Remove client from all groups
for (const [groupId, clientIds] of this.clientGroups) {
clientIds.delete(clientId);
// Delete group if empty
if (clientIds.size === 0) {
this.clientGroups.delete(groupId);
}
}
}
joinGroup(clientId, groupId) {
if (!this.clientGroups.has(groupId)) {
this.clientGroups.set(groupId, new Set());
}
this.clientGroups.get(groupId).add(clientId);
}
leaveGroup(clientId, groupId) {
if (this.clientGroups.has(groupId)) {
this.clientGroups.get(groupId).delete(clientId);
// Delete group if empty
if (this.clientGroups.get(groupId).size === 0) {
this.clientGroups.delete(groupId);
}
}
}
sendToClient(clientId, message) {
const ws = this.clients.get(clientId);
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
}
broadcast(message) {
for (const [clientId, ws] of this.clients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
}
}
multicast(groupId, message) {
if (this.clientGroups.has(groupId)) {
const clientIds = this.clientGroups.get(groupId);
for (const clientId of clientIds) {
this.sendToClient(clientId, message);
}
}
}
}
// Usage example
const manager = new WebSocketManager();
// Add clients
manager.addClient('client1', ws1);
manager.addClient('client2', ws2);
// Clients join group
manager.joinGroup('client1', 'group1');
manager.joinGroup('client2', 'group1');
// Send messages
manager.sendToClient('client1', 'Private message'); // Unicast
manager.broadcast('Broadcast message'); // Broadcast
manager.multicast('group1', 'Group message'); // Multicast



