Real-time Communication - সম্পূর্ণ Advanced Guide
Modern web applications এ real-time communication অপরিহার্য। এই guide এ WebSockets, Socket.io, এবং Server-Sent Events (SSE) সম্পূর্ণভাবে শিখবেন।
📑 Table of Contents
Foundation & Theory
WebSocket Protocol Deep Dive
- WebSocket Protocol Architecture
- HTTP to WebSocket Upgrade
- WebSocket Frame Structure
- WebSocket Protocol বেসিক
- Native WebSocket Implementation
Socket.io Theory & Architecture
- Socket.io Architecture Explained
- Engine.io vs Socket.io
- Transport Mechanisms
- Socket.io Setup & Basics
- Events & Event Handling
- Rooms & Namespaces
- Broadcasting Patterns
- Authentication & Authorization
Server-Sent Events
Advanced Topics
- Scalability & Load Balancing
- Redis Adapter for Multi-server
- Performance Optimization
- Security Best Practices
- Error Handling & Reconnection
- Production Deployment Patterns
Real-time Communication কি এবং কেন
What is Real-time Communication?
Real-time communication হলো একটি system যেখানে data instantly (বা প্রায় instantly) transmit হয় sender থেকে receiver এ। এটি traditional request-response model থেকে আলাদা।
// Traditional Model (Request-Response)
┌─────────┐ ┌─────────┐
│ Client │ ──── Request ────> │ Server │
│ │ <─── Response ──── │ │
└─────────┘ └─────────┘
// Real-time Model (Persistent Connection)
┌─────────┐ ┌─────────┐
│ Client │ <══ Connection ══> │ Server │
│ │ <── Push Data ─── │ │
│ │ ─── Send Data ──> │ │
└─────────┘ └─────────┘Why Real-time Communication?
1. Latency Reduction
Traditional Polling:
- Client asks every second: "Any update?"
- 99% responses: "No"
- Wasted bandwidth and server resources
Real-time:
- Server pushes only when there's actual data
- Zero unnecessary requests
- Instant delivery2. Better User Experience
// Example: Live Chat
Traditional: Refresh every 5 seconds to see new messages
Real-time: Messages appear instantly as they're sent
Result: Feels like real conversation3. Reduced Server Load
// 1000 users polling every second
Traditional: 1000 requests/second = 86,400,000 requests/day
// 1000 users with WebSocket
Real-time: 1000 connections, data sent only when neededHTTP এর Limitations
HTTP protocol যে problems তৈরি করে real-time communication এ।
1. Request-Response Model
/*
HTTP হলো একমুখী (unidirectional):
- Client must initiate every interaction
- Server cannot "push" data to client
- Each request needs new TCP connection (HTTP/1.1 এর আগে)
*/
// ❌ HTTP Limitation
Client: "Is there new data?" (Request)
Server: "No" (Response)
[Connection closes]
[5 seconds later]
Client: "Is there new data?" (Request)
Server: "No" (Response)
[Connection closes]
[5 seconds later]
Client: "Is there new data?" (Request)
Server: "Yes, here it is" (Response)
[Connection closes]
Total time to get update: Could be up to 5 seconds delay!2. Overhead Problem
/*
প্রতিটি HTTP request এ overhead থাকে:
HTTP Request Headers (~500-800 bytes):
GET /api/messages HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0...
Accept: application/json
Cookie: session=abc123...
Authorization: Bearer token...
[and more headers]
এই সব header প্রতিটি request এ পাঠাতে হয়!
*/
// 1 message poll per second for 1 hour:
// 3600 requests × 800 bytes headers = 2.88 MB
// Just for headers! No actual data.3. Scalability Issues
/*
10,000 users polling every second:
- 10,000 new TCP connections per second
- 10,000 × overhead
- Server must process & respond to all
- Most responses are "No new data"
Server CPU & Memory exhausted!
*/4. Battery Drain (Mobile)
/*
Mobile devices এ constant polling:
- Network radio stays active
- CPU processes responses
- Battery drains fast
Real-time connection:
- Single persistent connection
- Data comes when available
- Radio can sleep between messages
*/Communication Methods তুলনা
Real-time communication এর মাধ্যমে server এবং client instant data exchange করতে পারে।
1. Traditional vs Real-time
// ❌ Traditional Polling (Inefficient)
// Client repeatedly asks server for updates
setInterval(async () => {
const response = await fetch('/api/messages');
const messages = await response.json();
updateUI(messages);
}, 1000); // Every second - wasteful!
// ✅ Real-time with WebSocket
const socket = io('http://localhost:3000');
socket.on('new-message', (message) => {
updateUI(message);
// Instant update, no polling needed!
});2. Use Cases
// ✅ Real-time applications যেখানে প্রয়োজন:
// 1. Chat Applications
const chatSocket = io('/chat');
chatSocket.on('message', displayMessage);
// 2. Live Notifications
const notificationSocket = io('/notifications');
notificationSocket.on('notification', showNotification);
// 3. Collaborative Editing (Google Docs like)
const editorSocket = io('/editor');
editorSocket.on('text-change', updateDocument);
// 4. Live Sports/Stock Updates
const sportsSocket = io('/sports');
sportsSocket.on('score-update', updateScore);
// 5. Online Gaming
const gameSocket = io('/game');
gameSocket.on('player-move', updateGameState);
// 6. Live Tracking (Uber, Food Delivery)
const trackingSocket = io('/tracking');
trackingSocket.on('location-update', updateMap);3. Benefits
// ✅ Real-time communication এর সুবিধা:
// 1. Instant Updates
// No delay, immediate data push
// 2. Reduced Server Load
// No constant polling, fewer HTTP requests
// 3. Better User Experience
// Feels responsive and modern
// 4. Bi-directional Communication
// Both server and client can initiate
// 5. Efficient
// Persistent connection, less overheadCommunication Methods তুলনা
বিভিন্ন real-time techniques এর তুলনা।
1. HTTP Polling vs Long Polling vs WebSocket vs SSE
// ❌ HTTP Polling (Bad)
setInterval(async () => {
const data = await fetch('/api/data');
// Many unnecessary requests
}, 1000);
// 🤔 Long Polling (Better but not ideal)
async function longPoll() {
const response = await fetch('/api/long-poll');
const data = await response.json();
processData(data);
longPoll(); // Reconnect
}
// ✅ WebSocket (Best for bi-directional)
const ws = new WebSocket('ws://localhost:3000');
ws.onmessage = (event) => {
console.log('Received:', event.data);
};
ws.send('Hello Server'); // Can send anytime
// ✅ SSE (Best for server-to-client only)
const eventSource = new EventSource('/api/events');
eventSource.onmessage = (event) => {
console.log('Server sent:', event.data);
};2. Feature Comparison Table
/*
┌─────────────────┬──────────┬─────────────┬──────────┬─────┐
│ Feature │ Polling │ Long Polling│ WebSocket│ SSE │
├─────────────────┼──────────┼─────────────┼──────────┼─────┤
│ Real-time │ ❌ │ 🤔 │ ✅ │ ✅ │
│ Bi-directional │ ❌ │ ❌ │ ✅ │ ❌ │
│ Overhead │ High │ Medium │ Low │ Low │
│ Browser Support │ ✅ │ ✅ │ ✅ │ ✅ │
│ Firewall │ ✅ │ ✅ │ 🤔 │ ✅ │
│ Auto Reconnect │ N/A │ Manual │ Manual │ ✅ │
│ Use Case │ Legacy │ Fallback │ Chat │ Feed│
└─────────────────┴──────────┴─────────────┴──────────┴─────┘
*/WebSocket Protocol Architecture
WebSocket protocol এর internal architecture এবং কিভাবে কাজ করে।
1. WebSocket কি?
/*
WebSocket হলো একটি communication protocol যা:
- Full-duplex (দুই দিকে simultaneously data flow)
- Persistent connection (একবার connect, সবসময় open)
- Low latency (minimal overhead)
- Binary & Text data support
RFC 6455 specification follow করে
*/
// Protocol Identifier
ws://example.com/socket // Unencrypted
wss://example.com/socket // Encrypted (SSL/TLS)2. WebSocket vs HTTP Architecture
/*
┌─────────── HTTP Architecture ───────────┐
│ │
│ Client Server │
│ │ │ │
│ ├──── Request ───────────> │ │
│ │ (New TCP Connection) │ │
│ │ <──── Response ──────────┤ │
│ │ (Connection Closes) │ │
│ │ │ │
│ ├──── Request ───────────> │ │
│ │ (New TCP Connection) │ │
│ │ <──── Response ──────────┤ │
│ │ (Connection Closes) │ │
│ │
└──────────────────────────────────────────┘
┌─────────── WebSocket Architecture ──────┐
│ │
│ Client Server │
│ │ │ │
│ ├──── Handshake ─────────> │ │
│ │ <──── Upgrade ───────────┤ │
│ │ │ │
│ │ ═══════════════════════════ │
│ │ Persistent Connection │ │
│ │ ═══════════════════════════ │
│ │ │ │
│ ├──── Message ───────────> │ │
│ │ <──── Message ───────────┤ │
│ ├──── Message ───────────> │ │
│ │ <──── Message ───────────┤ │
│ │ │ │
│ │ (Connection stays open) │ │
│ │
└──────────────────────────────────────────┘
*/3. WebSocket Layers
/*
OSI Model এ WebSocket এর position:
Layer 7: Application Layer
├─ WebSocket Protocol
├─ HTTP (for initial handshake)
│
Layer 6: Presentation Layer
├─ SSL/TLS (for WSS)
│
Layer 5: Session Layer
│
Layer 4: Transport Layer
├─ TCP (WebSocket uses TCP)
│
Layer 3: Network Layer
├─ IP
│
Layer 2: Data Link Layer
Layer 1: Physical Layer
Key Point: WebSocket works on top of TCP
*/4. Connection Lifecycle
/*
┌─────────────────────────────────────────┐
│ WebSocket Connection Lifecycle │
└─────────────────────────────────────────┘
1. CONNECTING (readyState = 0)
↓
Client sends HTTP Upgrade request
↓
2. OPEN (readyState = 1)
↓
Handshake complete, connection established
↓
┌─────────────────────────────┐
│ Data Transfer Phase │
│ - Client → Server │
│ - Server → Client │
│ - Ping/Pong (heartbeat) │
└─────────────────────────────┘
↓
3. CLOSING (readyState = 2)
↓
Close frame sent/received
↓
4. CLOSED (readyState = 3)
↓
Connection terminated
*/5. Why WebSocket is Fast?
/*
HTTP Request/Response:
┌────────────────────────────────┐
│ TCP Handshake (3-way) │ ~50ms
│ TLS Handshake (if HTTPS) │ ~100ms
│ HTTP Headers (~800 bytes) │
│ Request Processing │
│ Response Headers (~500 bytes) │
│ Connection Close │
└────────────────────────────────┘
Total: ~150ms + processing time
EVERY SINGLE REQUEST!
WebSocket:
┌────────────────────────────────┐
│ Initial Handshake (once) │ ~150ms
├────────────────────────────────┤
│ Message Frame (~2-6 bytes) │ ~1ms
│ Data Payload │
└────────────────────────────────┘
Total: ~1ms per message (after handshake)
উদাহরণ: 100 messages পাঠাতে
HTTP: 100 × 150ms = 15 seconds
WebSocket: 150ms + (100 × 1ms) = 250ms
60x faster!
*/HTTP to WebSocket Upgrade
কিভাবে HTTP connection WebSocket এ upgrade হয়।
1. Upgrade Mechanism
/*
WebSocket connection শুরু হয় একটি HTTP request দিয়ে।
এই request টা special "Upgrade" header contain করে।
Flow:
1. Client sends HTTP GET with Upgrade header
2. Server responds with 101 Switching Protocols
3. Connection upgrades from HTTP to WebSocket
4. Now full-duplex communication possible
*/2. Client Handshake Request
GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com
Important Headers:
1. Upgrade: websocket
- Indicates client wants to upgrade connection
2. Connection: Upgrade
- Tells server this is an upgrade request
3. Sec-WebSocket-Key: [base64-encoded random bytes]
- Random value for security
- Prevents caching proxies from confusing connections
- Server uses this to prove it understands WebSocket
4. Sec-WebSocket-Version: 13
- WebSocket protocol version (13 is current)
5. Origin: http://example.com
- Security feature (CORS-like)
- Server can verify origin3. Server Handshake Response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Important Headers:
1. Status: 101 Switching Protocols
- Success! Connection is upgrading
2. Upgrade: websocket
- Confirms upgrade to WebSocket
3. Connection: Upgrade
- Confirms connection upgrade
4. Sec-WebSocket-Accept: [calculated hash]
- Proves server understands WebSocket protocol
- Calculated as: Base64(SHA1(Sec-WebSocket-Key + Magic String))
- Magic String: "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"4. Sec-WebSocket-Accept Calculation
// ✅ কিভাবে server calculate করে
const crypto = require('crypto');
function calculateWebSocketAccept(key) {
// Magic GUID defined in RFC 6455
const MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
// Concatenate key with magic string
const combined = key + MAGIC_STRING;
// SHA1 hash
const hash = crypto
.createHash('sha1')
.update(combined)
.digest();
// Base64 encode
const accept = hash.toString('base64');
return accept;
}
// Example
const clientKey = 'dGhlIHNhbXBsZSBub25jZQ==';
const acceptKey = calculateWebSocketAccept(clientKey);
console.log(acceptKey);
// Output: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
/*
Why this calculation?
- Proves server truly understands WebSocket
- Prevents cross-protocol attacks
- Simple but effective security measure
*/📝 Hands-on Example: Testing Upgrade Manually
// ✅ Example 1: Simulate WebSocket handshake with Node.js
const crypto = require('crypto');
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/websocket' && req.headers.upgrade === 'websocket') {
// Get client key
const key = req.headers['sec-websocket-key'];
// Calculate accept key
const MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const accept = crypto
.createHash('sha1')
.update(key + MAGIC)
.digest('base64');
// Send 101 Switching Protocols
const headers = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
`Sec-WebSocket-Accept: ${accept}`,
'',
''
].join('\r\n');
res.socket.write(headers);
console.log('✅ Upgraded to WebSocket!');
console.log('Client Key:', key);
console.log('Accept Key:', accept);
// Now you can send/receive WebSocket frames
} else {
res.writeHead(200);
res.end('Use WebSocket client to connect to /websocket');
}
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
console.log('Test with: wscat -c ws://localhost:3000/websocket');
});
/*
Test করুন:
1. npm install -g wscat
2. wscat -c ws://localhost:3000/websocket
3. Console এ upgrade process দেখুন
*/// ✅ Example 2: Browser থেকে test করুন
// client.html
/*
<!DOCTYPE html>
<html>
<head><title>WebSocket Handshake Test</title></head>
<body>
<h1>WebSocket Upgrade Test</h1>
<button onclick="connect()">Connect</button>
<div id="status">Not connected</div>
<div id="log"></div>
<script>
function connect() {
const ws = new WebSocket('ws://localhost:3000/websocket');
const log = document.getElementById('log');
const status = document.getElementById('status');
// ✅ Connection opening - Upgrade happening
ws.addEventListener('open', (event) => {
status.textContent = '✅ Connected (Upgraded)';
status.style.color = 'green';
log.innerHTML += '<p>✅ HTTP upgraded to WebSocket!</p>';
log.innerHTML += '<p>ReadyState: ' + ws.readyState + ' (OPEN)</p>';
// Send test message
ws.send('Hello from browser!');
});
ws.addEventListener('message', (event) => {
log.innerHTML += '<p>📩 Received: ' + event.data + '</p>';
});
ws.addEventListener('close', (event) => {
status.textContent = '❌ Disconnected';
status.style.color = 'red';
log.innerHTML += '<p>Connection closed</p>';
});
ws.addEventListener('error', (error) => {
log.innerHTML += '<p>❌ Error: ' + error + '</p>';
});
}
</script>
</body>
</html>
*/
/*
📊 Observe করুন:
1. Browser DevTools → Network tab open করুন
2. "Connect" button click করুন
3. Network tab এ WebSocket connection দেখুন
4. Headers tab এ upgrade headers দেখুন:
- Request: Upgrade: websocket, Sec-WebSocket-Key
- Response: 101 Switching Protocols, Sec-WebSocket-Accept
5. Messages tab এ frames দেখুন
*/// ✅ Example 3: Upgrade failure scenarios
const http = require('http');
const server = http.createServer((req, res) => {
// Scenario 1: Missing Upgrade header
if (!req.headers.upgrade) {
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Bad Request: Missing Upgrade header');
console.log('❌ Rejected: No upgrade header');
return;
}
// Scenario 2: Wrong upgrade value
if (req.headers.upgrade !== 'websocket') {
res.writeHead(400);
res.end('Bad Request: Invalid Upgrade value');
console.log('❌ Rejected: Wrong upgrade value');
return;
}
// Scenario 3: Unsupported version
const version = req.headers['sec-websocket-version'];
if (version !== '13') {
res.writeHead(426, {
'Sec-WebSocket-Version': '13'
});
res.end('Upgrade Required: Use WebSocket version 13');
console.log('❌ Rejected: Unsupported version', version);
return;
}
// Scenario 4: Origin check (security)
const origin = req.headers.origin;
const allowedOrigins = ['http://localhost:3000', 'http://127.0.0.1:3000'];
if (origin && !allowedOrigins.includes(origin)) {
res.writeHead(403);
res.end('Forbidden: Origin not allowed');
console.log('❌ Rejected: Invalid origin', origin);
return;
}
// ✅ All checks passed - upgrade!
console.log('✅ All validations passed, upgrading...');
const key = req.headers['sec-websocket-key'];
const accept = calculateAccept(key);
res.socket.write([
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
`Sec-WebSocket-Accept: ${accept}`,
'',
''
].join('\r\n'));
});
function calculateAccept(key) {
const crypto = require('crypto');
return crypto
.createHash('sha1')
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
.digest('base64');
}
server.listen(3000);
/*
Test different scenarios:
1. Valid upgrade:
wscat -c ws://localhost:3000
✅ Success!
2. Wrong version (if you could):
Headers: Sec-WebSocket-Version: 12
❌ 426 Upgrade Required
3. Missing headers:
curl http://localhost:3000
❌ 400 Bad Request
4. Wrong origin (from browser):
Open from file:// protocol
❌ 403 Forbidden
*/5. Upgrade Process Step-by-Step
/*
Step-by-Step Upgrade:
┌─────────────────────────────────────────┐
│ 1. TCP Connection Established │
├─────────────────────────────────────────┤
│ Client: SYN │
│ Server: SYN-ACK │
│ Client: ACK │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 2. HTTP Upgrade Request │
├─────────────────────────────────────────┤
│ GET /socket HTTP/1.1 │
│ Upgrade: websocket │
│ Connection: Upgrade │
│ Sec-WebSocket-Key: xxx │
│ Sec-WebSocket-Version: 13 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 3. Server Validates Request │
├─────────────────────────────────────────┤
│ - Check Origin (security) │
│ - Verify WebSocket version │
│ - Calculate Sec-WebSocket-Accept │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 4. Server Sends 101 Response │
├─────────────────────────────────────────┤
│ HTTP/1.1 101 Switching Protocols │
│ Upgrade: websocket │
│ Connection: Upgrade │
│ Sec-WebSocket-Accept: yyy │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 5. Connection Upgraded! │
├─────────────────────────────────────────┤
│ HTTP protocol stops │
│ WebSocket protocol begins │
│ Full-duplex communication enabled │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 6. WebSocket Frames Exchange │
├─────────────────────────────────────────┤
│ Client ←→ Server (bidirectional) │
└─────────────────────────────────────────┘
*/6. Upgrade Failures
/*
Common upgrade failures:
1. Missing Headers
HTTP/1.1 400 Bad Request
- Missing Upgrade or Connection header
2. Version Mismatch
HTTP/1.1 426 Upgrade Required
Sec-WebSocket-Version: 13
- Client using unsupported version
3. Origin Rejected
HTTP/1.1 403 Forbidden
- Server doesn't accept this origin
4. Server Error
HTTP/1.1 500 Internal Server Error
- Server-side issue during upgrade
*/WebSocket Frame Structure
WebSocket data কিভাবে frames আকারে transfer হয়।
1. Frame Format
/*
WebSocket Frame Structure (RFC 6455):
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 এর ব্যাখ্যা নিচে...
*/2. Frame Fields Explained
/*
1. FIN (1 bit):
- Final fragment indicator
- 1 = This is the last fragment
- 0 = More fragments coming
Use case: Large messages can be split into fragments
2. RSV1, RSV2, RSV3 (1 bit each):
- Reserved for future use
- Must be 0 unless extension defined
3. Opcode (4 bits):
- Defines frame type
- Values:
0x0 = Continuation frame
0x1 = Text frame (UTF-8)
0x2 = Binary frame
0x8 = Connection close
0x9 = Ping
0xA = Pong
4. MASK (1 bit):
- Is payload masked?
- Client → Server: MUST be 1 (masked)
- Server → Client: MUST be 0 (not masked)
Why? Security against cache poisoning attacks
5. Payload Length (7 bits, or 7+16, or 7+64):
- If 0-125: That's the length
- If 126: Next 16 bits are length
- If 127: Next 64 bits are length
Examples:
- 10 bytes = 10
- 200 bytes = 126, then 200
- 70000 bytes = 127, then 70000
6. Masking Key (32 bits):
- Random key for XOR masking
- Only present if MASK = 1
- Changes with every frame from client
7. Payload Data:
- Actual message data
- Masked if from client
- Unmasked if from server
*/3. Frame Types Examples
// Text Frame (Client → Server)
/*
┌─────────────────────────────────┐
│ FIN: 1 (final fragment) │
│ Opcode: 0x1 (text) │
│ MASK: 1 (masked) │
│ Payload Length: 5 │
│ Masking Key: [random 4 bytes] │
│ Payload: "Hello" (masked) │
└─────────────────────────────────┘
Actual bytes (hex):
81 85 [mask] [masked "Hello"]
│ │ │ │
│ │ │ └─ Masked payload
│ │ └──────── Masking key (4 bytes)
│ └───────────── Length (0x85 = 128+5 = masked + 5 bytes)
└──────────────── FIN=1, Opcode=1 (0x81 = 10000001)
*/
// Ping Frame (Server → Client)
/*
┌─────────────────────────────────┐
│ FIN: 1 │
│ Opcode: 0x9 (ping) │
│ MASK: 0 (not masked) │
│ Payload Length: 0 │
└─────────────────────────────────┘
Actual bytes (hex):
89 00
│ │
│ └─ Length (0 bytes)
└──── FIN=1, Opcode=9 (0x89 = 10001001)
*/
// Close Frame
/*
┌─────────────────────────────────┐
│ FIN: 1 │
│ Opcode: 0x8 (close) │
│ MASK: 0 │
│ Payload Length: 2 │
│ Payload: Close code (1000) │
└─────────────────────────────────┘
Close Codes:
1000 = Normal closure
1001 = Going away
1002 = Protocol error
1003 = Unsupported data
1006 = Abnormal closure
1007 = Invalid data
1008 = Policy violation
1009 = Message too big
1010 = Extension negotiation failed
1011 = Server error
*/4. Masking Algorithm
// ✅ কিভাবে masking কাজ করে
function maskPayload(payload, maskingKey) {
const masked = Buffer.alloc(payload.length);
for (let i = 0; i < payload.length; i++) {
// XOR each byte with corresponding masking key byte
masked[i] = payload[i] ^ maskingKey[i % 4];
}
return masked;
}
// Unmask (same algorithm!)
function unmaskPayload(maskedPayload, maskingKey) {
return maskPayload(maskedPayload, maskingKey);
// XOR is reversible: (X ^ Y) ^ Y = X
}
// Example
const message = Buffer.from('Hello');
const maskingKey = Buffer.from([0x12, 0x34, 0x56, 0x78]);
const masked = maskPayload(message, maskingKey);
console.log('Masked:', masked.toString('hex'));
const unmasked = unmaskPayload(masked, maskingKey);
console.log('Unmasked:', unmasked.toString()); // "Hello"
/*
Why masking?
- Prevents proxy cache poisoning
- Ensures each frame is unique
- Security against certain attacks
*/5. Message Fragmentation
/*
Large messages can be split into fragments:
Original: "This is a very long message"
Fragment 1:
┌─────────────────────────────────┐
│ FIN: 0 (not final) │
│ Opcode: 0x1 (text) │
│ Payload: "This is a" │
└─────────────────────────────────┘
Fragment 2:
┌─────────────────────────────────┐
│ FIN: 0 (not final) │
│ Opcode: 0x0 (continuation) │
│ Payload: " very long" │
└─────────────────────────────────┘
Fragment 3:
┌─────────────────────────────────┐
│ FIN: 1 (final!) │
│ Opcode: 0x0 (continuation) │
│ Payload: " message" │
└─────────────────────────────────┘
Receiver assembles: "This is a very long message"
Benefits:
- Can send data as it becomes available
- Better memory management
- Lower latency for first byte
*/6. Control Frames
/*
Control frames are special:
1. Must have FIN = 1 (not fragmented)
2. Payload ≤ 125 bytes
3. Can be injected between data fragments
Types:
PING (0x9):
- Heartbeat mechanism
- Check if connection alive
- Server or client can send
PONG (0xA):
- Response to PING
- Must echo PING's payload
- Proves connection is alive
CLOSE (0x8):
- Graceful connection termination
- Contains close code & reason
- Both sides must send CLOSE
*/
// Heartbeat example
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping(); // Send PING frame
}
}, 30000);
ws.on('pong', () => {
console.log('Connection alive');
lastPong = Date.now();
});📝 Hands-on Example: Understanding Frames Practically
// ✅ Example 1: Decode WebSocket frame manually
function decodeFrame(buffer) {
console.log('Raw bytes:', buffer.toString('hex'));
console.log('Binary:', buffer.toString('binary'));
// Byte 0: FIN + Opcode
const byte0 = buffer[0];
const fin = (byte0 & 0b10000000) >> 7; // First bit
const opcode = byte0 & 0b00001111; // Last 4 bits
console.log('FIN:', fin, fin ? '(Final)' : '(More coming)');
console.log('Opcode:', opcode, getOpcodeType(opcode));
// Byte 1: MASK + Payload length
const byte1 = buffer[1];
const masked = (byte1 & 0b10000000) >> 7;
let payloadLength = byte1 & 0b01111111;
console.log('MASK:', masked, masked ? '(Masked)' : '(Not masked)');
console.log('Payload length:', payloadLength);
let offset = 2;
// Extended payload length
if (payloadLength === 126) {
payloadLength = buffer.readUInt16BE(2);
offset = 4;
console.log('Extended length (16-bit):', payloadLength);
} else if (payloadLength === 127) {
payloadLength = buffer.readBigUInt64BE(2);
offset = 10;
console.log('Extended length (64-bit):', payloadLength);
}
// Masking key (if masked)
let maskingKey;
if (masked) {
maskingKey = buffer.slice(offset, offset + 4);
offset += 4;
console.log('Masking key:', maskingKey.toString('hex'));
}
// Payload
let payload = buffer.slice(offset, offset + payloadLength);
// Unmask if needed
if (masked && maskingKey) {
const unmasked = Buffer.alloc(payload.length);
for (let i = 0; i < payload.length; i++) {
unmasked[i] = payload[i] ^ maskingKey[i % 4];
}
payload = unmasked;
}
console.log('Payload (decoded):', payload.toString());
console.log('---');
return {
fin,
opcode,
masked,
payloadLength,
payload: payload.toString()
};
}
function getOpcodeType(opcode) {
const types = {
0: 'Continuation',
1: 'Text',
2: 'Binary',
8: 'Close',
9: 'Ping',
10: 'Pong'
};
return types[opcode] || 'Unknown';
}
// Test it!
// Text frame: "Hello"
const textFrame = Buffer.from([
0x81, // FIN=1, Opcode=1 (text)
0x05, // MASK=0, Length=5
0x48, 0x65, 0x6c, 0x6c, 0x6f // "Hello"
]);
console.log('=== Decoding Text Frame ===');
decodeFrame(textFrame);
// Ping frame
const pingFrame = Buffer.from([
0x89, // FIN=1, Opcode=9 (ping)
0x00 // MASK=0, Length=0
]);
console.log('=== Decoding Ping Frame ===');
decodeFrame(pingFrame);
/*
Output:
=== Decoding Text Frame ===
Raw bytes: 810548656c6c6f
Binary: ...
FIN: 1 (Final)
Opcode: 1 Text
MASK: 0 (Not masked)
Payload length: 5
Payload (decoded): Hello
---
=== Decoding Ping Frame ===
Raw bytes: 8900
FIN: 1 (Final)
Opcode: 9 Ping
MASK: 0 (Not masked)
Payload length: 0
---
*/// ✅ Example 2: Create WebSocket frame manually
function createTextFrame(text, masked = false) {
const payload = Buffer.from(text);
const payloadLength = payload.length;
// Calculate total frame size
let frameSize = 2 + payloadLength;
if (masked) frameSize += 4; // Masking key
if (payloadLength > 125) frameSize += 2; // Extended length
if (payloadLength > 65535) frameSize += 6; // 64-bit length
const frame = Buffer.alloc(frameSize);
let offset = 0;
// Byte 0: FIN (1) + Opcode (1 for text)
frame[offset++] = 0b10000001; // 0x81
// Byte 1: MASK + Payload length
let byte1 = 0;
if (masked) byte1 |= 0b10000000; // Set MASK bit
if (payloadLength <= 125) {
byte1 |= payloadLength;
frame[offset++] = byte1;
} else if (payloadLength <= 65535) {
byte1 |= 126;
frame[offset++] = byte1;
frame.writeUInt16BE(payloadLength, offset);
offset += 2;
} else {
byte1 |= 127;
frame[offset++] = byte1;
frame.writeBigUInt64BE(BigInt(payloadLength), offset);
offset += 8;
}
// Masking key (if masked)
let maskingKey;
if (masked) {
maskingKey = Buffer.from([
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256)
]);
maskingKey.copy(frame, offset);
offset += 4;
}
// Payload (masked if needed)
if (masked && maskingKey) {
for (let i = 0; i < payload.length; i++) {
frame[offset++] = payload[i] ^ maskingKey[i % 4];
}
} else {
payload.copy(frame, offset);
}
return frame;
}
// Test
const frame1 = createTextFrame('Hello', false);
console.log('Unmasked frame:', frame1.toString('hex'));
// Output: 810548656c6c6f
const frame2 = createTextFrame('Hello', true);
console.log('Masked frame:', frame2.toString('hex'));
// Output: 8185[random 4 bytes][masked payload]
// Verify by decoding
console.log('\nDecoding our created frame:');
decodeFrame(frame1);// ✅ Example 3: Fragmented message
function createFragmentedMessage(text, chunkSize = 5) {
const payload = Buffer.from(text);
const fragments = [];
for (let i = 0; i < payload.length; i += chunkSize) {
const chunk = payload.slice(i, i + chunkSize);
const isLast = (i + chunkSize >= payload.length);
const frame = Buffer.alloc(2 + chunk.length);
// First fragment: Opcode = 1 (text), FIN = 0
// Middle fragments: Opcode = 0 (continuation), FIN = 0
// Last fragment: Opcode = 0 (continuation), FIN = 1
const opcode = (i === 0) ? 1 : 0;
const fin = isLast ? 1 : 0;
frame[0] = (fin << 7) | opcode;
frame[1] = chunk.length;
chunk.copy(frame, 2);
fragments.push(frame);
console.log(`Fragment ${fragments.length}:`, {
fin: fin ? 'FINAL' : 'MORE',
opcode: opcode === 1 ? 'TEXT' : 'CONTINUATION',
payload: chunk.toString(),
hex: frame.toString('hex')
});
}
return fragments;
}
// Test
console.log('=== Creating Fragmented Message ===');
const fragments = createFragmentedMessage('Hello World!', 5);
/*
Output:
Fragment 1: { fin: 'MORE', opcode: 'TEXT', payload: 'Hello', hex: '010548656c6c6f' }
Fragment 2: { fin: 'MORE', opcode: 'CONTINUATION', payload: ' Worl', hex: '0005205776f726c' }
Fragment 3: { fin: 'FINAL', opcode: 'CONTINUATION', payload: 'd!', hex: '80026421' }
Receiver assembles: "Hello" + " Worl" + "d!" = "Hello World!"
*/// ✅ Example 4: Real WebSocket server with frame logging
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });
wss.on('connection', (ws) => {
console.log('Client connected');
// Intercept raw frames
ws._socket.on('data', (buffer) => {
console.log('\n📥 Received frame:');
console.log('Hex:', buffer.toString('hex'));
// Decode it
const decoded = decodeFrame(buffer);
console.log('Decoded:', decoded);
});
ws.on('message', (data) => {
console.log('Message:', data.toString());
// Send pong
ws.ping();
});
ws.on('ping', () => {
console.log('📍 Ping received');
});
ws.on('pong', () => {
console.log('📍 Pong received');
});
ws.on('close', (code, reason) => {
console.log('Connection closed:', code, reason.toString());
});
});
console.log('WebSocket server running on ws://localhost:3000');
console.log('Connect with: wscat -c ws://localhost:3000');
/*
Test করুন:
Terminal 1:
node server.js
Terminal 2:
wscat -c ws://localhost:3000
Type: Hello
Watch Terminal 1 for frame breakdown!
You'll see:
📥 Received frame:
Hex: 8185a1b2c3d4e9d7a6b1c8
FIN: 1 (Final)
Opcode: 1 Text
MASK: 1 (Masked)
Masking key: a1b2c3d4
Payload (decoded): Hello
---
Message: Hello
*/WebSocket Protocol বেসিক
WebSocket হলো একটা protocol যা persistent, bi-directional connection provide করে।
1. WebSocket Lifecycle
// Client Side (Browser)
const ws = new WebSocket('ws://localhost:3000');
// 1. Connection Opening
ws.addEventListener('open', (event) => {
console.log('Connected to server');
ws.send('Hello Server!');
});
// 2. Receiving Messages
ws.addEventListener('message', (event) => {
console.log('Message from server:', event.data);
});
// 3. Connection Close
ws.addEventListener('close', (event) => {
console.log('Disconnected:', event.code, event.reason);
});
// 4. Error Handling
ws.addEventListener('error', (event) => {
console.error('WebSocket error:', event);
});
// 5. Sending Messages
function sendMessage(message) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(message));
} else {
console.log('WebSocket not ready');
}
}2. WebSocket States
// ✅ Understanding WebSocket ReadyState
function checkWebSocketState(ws) {
switch(ws.readyState) {
case WebSocket.CONNECTING: // 0
console.log('Connecting...');
break;
case WebSocket.OPEN: // 1
console.log('Connected and ready');
break;
case WebSocket.CLOSING: // 2
console.log('Connection closing...');
break;
case WebSocket.CLOSED: // 3
console.log('Connection closed');
break;
}
}
// Usage
const ws = new WebSocket('ws://localhost:3000');
ws.addEventListener('open', () => {
console.log('State:', ws.readyState); // 1 (OPEN)
});
// Before sending, always check
if (ws.readyState === WebSocket.OPEN) {
ws.send('Message');
}Native WebSocket Implementation
Built-in WebSocket দিয়ে server এবং client implementation।
1. Basic WebSocket Server
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });
console.log('WebSocket server running on port 3000');
wss.on('connection', (ws) => {
console.log('New client connected');
// Send welcome message
ws.send(JSON.stringify({
type: 'welcome',
message: 'Welcome to WebSocket server!'
}));
// Receive messages
ws.on('message', (data) => {
console.log('Received:', data.toString());
// Echo back
ws.send(JSON.stringify({
type: 'echo',
message: data.toString()
}));
});
// Handle disconnect
ws.on('close', () => {
console.log('Client disconnected');
});
// Handle errors
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
});2. Client Implementation
<!-- client.html -->
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Client</title>
</head>
<body>
<h1>WebSocket Demo</h1>
<div id="status">Disconnected</div>
<input type="text" id="messageInput" placeholder="Type message...">
<button onclick="sendMessage()">Send</button>
<div id="messages"></div>
<script>
const ws = new WebSocket('ws://localhost:3000');
const status = document.getElementById('status');
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
ws.addEventListener('open', () => {
status.textContent = 'Connected';
status.style.color = 'green';
});
ws.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
const messageDiv = document.createElement('div');
messageDiv.textContent = `${data.type}: ${data.message}`;
messages.appendChild(messageDiv);
});
ws.addEventListener('close', () => {
status.textContent = 'Disconnected';
status.style.color = 'red';
});
ws.addEventListener('error', (error) => {
console.error('Error:', error);
});
function sendMessage() {
const message = messageInput.value;
if (message && ws.readyState === WebSocket.OPEN) {
ws.send(message);
messageInput.value = '';
}
}
// Send on Enter key
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>3. Broadcasting to All Clients
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });
// Track all connected clients
const clients = new Set();
wss.on('connection', (ws) => {
// Add new client
clients.add(ws);
console.log(`Total clients: ${clients.size}`);
// Broadcast to all clients
broadcast({
type: 'user-joined',
count: clients.size
});
ws.on('message', (data) => {
const message = data.toString();
// Broadcast message to all clients
broadcast({
type: 'message',
content: message,
timestamp: new Date().toISOString()
});
});
ws.on('close', () => {
// Remove client
clients.delete(ws);
// Notify others
broadcast({
type: 'user-left',
count: clients.size
});
});
});
function broadcast(data) {
const message = JSON.stringify(data);
clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}4. WebSocket with Express
// server.js
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
// Serve static files
app.use(express.static('public'));
// Regular HTTP routes
app.get('/api/status', (req, res) => {
res.json({
clients: wss.clients.size,
uptime: process.uptime()
});
});
// WebSocket connection
wss.on('connection', (ws, req) => {
const ip = req.socket.remoteAddress;
console.log('New connection from:', ip);
ws.on('message', (data) => {
console.log('Received:', data.toString());
// Broadcast to all except sender
wss.clients.forEach((client) => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});Socket.io Architecture Explained
Socket.io এর internal architecture এবং কিভাবে কাজ করে তার বিস্তারিত।
1. Socket.io কি?
/*
Socket.io হলো একটি library (not a protocol!) যা:
1. WebSocket এর উপর build
2. Additional features provide করে
3. Fallback mechanisms আছে
4. Cross-browser compatibility
5. Auto-reconnection
6. Room & Namespace support
7. Broadcasting helpers
Architecture:
┌─────────────────────────────────┐
│ Socket.io Library │
├─────────────────────────────────┤
│ Engine.io (Core) │
├─────────────────────────────────┤
│ WebSocket │ HTTP Long-Poll │
├─────────────┴───────────────────┤
│ TCP/IP │
└─────────────────────────────────┘
*/2. Socket.io Layers
/*
3-Layer Architecture:
Layer 1: Socket.io (High-level API)
├─ Namespaces
├─ Rooms
├─ Acknowledgments
├─ Broadcasting
├─ Middleware
└─ Event-based API
Layer 2: Engine.io (Transport layer)
├─ Connection establishment
├─ Transport negotiation
├─ Heartbeat mechanism
├─ Upgrade logic (polling → WebSocket)
└─ Binary support
Layer 3: Transport (Actual communication)
├─ WebSocket (preferred)
└─ HTTP Long-polling (fallback)
*/3. কেন Socket.io ব্যবহার করবেন?
/*
Native WebSocket vs Socket.io:
Native WebSocket:
┌────────────────────────────────┐
│ ✅ Faster (less overhead) │
│ ✅ Simple protocol │
│ ❌ No fallback │
│ ❌ No auto-reconnect │
│ ❌ No rooms/namespaces │
│ ❌ Manual heartbeat │
│ ❌ More code to write │
└────────────────────────────────┘
Socket.io:
┌────────────────────────────────┐
│ ✅ Automatic fallback │
│ ✅ Auto-reconnection │
│ ✅ Built-in rooms/namespaces │
│ ✅ Automatic heartbeat │
│ ✅ Broadcasting helpers │
│ ✅ Middleware support │
│ ❌ Slightly more overhead │
└────────────────────────────────┘
Verdict: Socket.io for production, Native for performance-critical
*/4. Connection Flow
/*
Socket.io Connection Process:
┌─────────────────────────────────────┐
│ 1. Client Initiates Connection │
├─────────────────────────────────────┤
│ const socket = io('http://server'); │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. Engine.io Handshake │
├─────────────────────────────────────┤
│ GET /socket.io/?EIO=4&transport=... │
│ Server responds with session ID │
│ (Uses HTTP Long-polling first) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. Transport Upgrade Attempt │
├─────────────────────────────────────┤
│ If WebSocket available: │
│ Try to upgrade to WebSocket │
│ If successful: │
│ Close polling, use WebSocket │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4. Socket.io Handshake │
├─────────────────────────────────────┤
│ Exchange Socket.io metadata │
│ Establish namespaces │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 5. Connection Established │
├─────────────────────────────────────┤
│ socket.on('connect') fires │
│ Ready for communication │
└─────────────────────────────────────┘
*/5. Packet Structure
/*
Socket.io packets format:
[Packet Type][Namespace][Data]
Packet Types:
0 = CONNECT
1 = DISCONNECT
2 = EVENT
3 = ACK
4 = ERROR
5 = BINARY_EVENT
6 = BINARY_ACK
Example packets:
// Connect to default namespace
0
// Connect to /chat namespace
0/chat
// Event with data
2["message",{"text":"Hello"}]
│ │ │
│ │ └─ Data (JSON)
│ └──────────── Event name
└────────────── Packet type (EVENT)
// Event with acknowledgment
21["getData",{}]
│││ │
│││ └─ Data
││└─────────── Event name
│└──────────── Packet ID (for ACK)
└───────────── Packet type (EVENT)
// Acknowledgment response
3["data received"]
││
│└─ ACK data
└── Packet type (ACK)
*/Engine.io vs Socket.io
Engine.io এবং Socket.io এর পার্থক্য এবং relationship।
1. Relationship
/*
┌───────────────────────────────────────┐
│ Your Application │
│ (Events, Rooms, Broadcasting, etc.) │
└────────────────┬──────────────────────┘
│
│ Uses
↓
┌───────────────────────────────────────┐
│ Socket.io │
│ • High-level API │
│ • Namespaces & Rooms │
│ • Event emitter pattern │
│ • Middleware support │
└────────────────┬──────────────────────┘
│
│ Built on
↓
┌───────────────────────────────────────┐
│ Engine.io │
│ • Low-level transport │
│ • Connection management │
│ • Heartbeat/ping-pong │
│ • Transport upgrade │
└────────────────┬──────────────────────┘
│
│ Uses
↓
┌───────────────────────────────────────┐
│ WebSocket / HTTP Long-polling │
└───────────────────────────────────────┘
Analogy:
Engine.io = Car Engine (transport mechanism)
Socket.io = Complete Car (features + engine)
*/2. Engine.io Responsibilities
/*
Engine.io handles:
1. Transport Selection
├─ Try WebSocket first
├─ Fallback to polling if needed
└─ Upgrade polling to WebSocket when possible
2. Connection Management
├─ Session establishment
├─ Session ID generation
└─ Connection state tracking
3. Heartbeat
├─ Send ping packets
├─ Expect pong responses
└─ Detect dead connections
4. Message Encoding/Decoding
├─ Convert to Engine.io packet format
└─ Parse incoming packets
5. Buffer Management
├─ Queue messages during upgrade
└─ Flush when upgrade complete
*/3. Socket.io Responsibilities
/*
Socket.io handles:
1. Event System
├─ Event emitter API
├─ Custom event names
└─ Event acknowledgments
2. Namespaces
├─ Logical separation
├─ Different contexts
└─ Authorization per namespace
3. Rooms
├─ Grouping sockets
├─ Targeted broadcasting
└─ Dynamic room membership
4. Broadcasting
├─ To all sockets
├─ To specific rooms
├─ Excluding sender
└─ To specific namespaces
5. Middleware
├─ Authentication
├─ Authorization
└─ Request preprocessing
6. Auto-reconnection
├─ Retry logic
├─ Backoff strategy
└─ State restoration
*/4. Packet Flow
/*
When you emit an event:
Your Code:
socket.emit('message', { text: 'Hello' });
↓
Socket.io Layer:
┌──────────────────────────────────┐
│ 1. Wrap in Socket.io packet │
│ Format: 2["message",{"text...}]│
│ 2. Add namespace if needed │
│ 3. Handle acknowledgment if any │
└──────────────────────────────────┘
↓
Engine.io Layer:
┌──────────────────────────────────┐
│ 1. Encode to Engine.io format │
│ Format: 4[Socket.io packet] │
│ 2. Check connection state │
│ 3. Select transport │
└──────────────────────────────────┘
↓
Transport Layer:
┌──────────────────────────────────┐
│ WebSocket: │
│ Send as WebSocket frame │
│ OR │
│ Polling: │
│ Queue until client polls │
└──────────────────────────────────┘
↓
Network → Server
*/📝 Hands-on Example: Observe Socket.io Packets
// ✅ Example 1: Log Socket.io packet flow
// server.js
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server);
// Intercept Engine.io packets
io.engine.on('packet', ({ type, data }) => {
console.log('📦 Engine.io packet:', {
type: ['open', 'close', 'ping', 'pong', 'message', 'upgrade', 'noop'][type],
data
});
});
io.on('connection', (socket) => {
console.log('✅ Socket.io connected:', socket.id);
console.log('Transport:', socket.conn.transport.name);
// Log Socket.io packets
socket.onAny((eventName, ...args) => {
console.log('📨 Socket.io event:', eventName, args);
});
socket.on('my-event', (data) => {
console.log('Received my-event:', data);
// Send response
socket.emit('response', { message: 'Got it!' });
});
// Watch transport changes
socket.conn.on('upgrade', (transport) => {
console.log('⬆️ Upgraded to:', transport.name);
});
});
server.listen(3000, () => {
console.log('Server on http://localhost:3000');
});
// client.html
/*
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io({
// Enable debugging
transports: ['polling', 'websocket']
});
socket.on('connect', () => {
console.log('Connected:', socket.id);
console.log('Transport:', socket.io.engine.transport.name);
// Send event
socket.emit('my-event', { foo: 'bar' });
});
socket.on('response', (data) => {
console.log('Response:', data);
});
// Log all packets (debugging)
socket.io.engine.on('packet', (packet) => {
console.log('Packet:', packet);
});
socket.io.engine.on('packetCreate', (packet) => {
console.log('Sending:', packet);
});
</script>
*/
/*
Console output sequence:
Server:
📦 Engine.io packet: { type: 'message', data: '0' }
✅ Socket.io connected: abc123
Transport: polling
📨 Socket.io event: my-event [ { foo: 'bar' } ]
Received my-event: { foo: 'bar' }
⬆️ Upgraded to: websocket
📦 Engine.io packet: { type: 'message', data: '2["response",{"message":"Got it!"}]' }
Client:
Connected: abc123
Transport: polling
Packet: { type: 'message', data: '0' }
Sending: { type: 'message', data: '2["my-event",{"foo":"bar"}]' }
Transport upgraded to: websocket
Response: { message: 'Got it!' }
*/// ✅ Example 2: Packet format decoder
function decodeSocketIOPacket(packet) {
console.log('Raw packet:', packet);
// Engine.io packet format: [type][data]
const engineType = packet[0];
const engineTypes = ['open', 'close', 'ping', 'pong', 'message', 'upgrade', 'noop'];
console.log('Engine.io type:', engineTypes[engineType]);
if (engineType === 4) { // message
const socketIOData = packet.slice(1);
console.log('Socket.io data:', socketIOData);
// Socket.io packet format: [type][id?][namespace][data]
const socketIOType = socketIOData[0];
const socketIOTypes = ['CONNECT', 'DISCONNECT', 'EVENT', 'ACK', 'ERROR', 'BINARY_EVENT', 'BINARY_ACK'];
console.log('Socket.io type:', socketIOTypes[socketIOType]);
// Parse rest
const rest = socketIOData.slice(1);
if (socketIOType === 2) { // EVENT
try {
const eventData = JSON.parse(rest);
console.log('Event name:', eventData[0]);
console.log('Event data:', eventData[1]);
} catch (e) {
console.log('Data:', rest);
}
}
}
console.log('---');
}
// Test different packets
decodeSocketIOPacket('0'); // CONNECT
decodeSocketIOPacket('2["message",{"text":"hello"}]'); // EVENT
decodeSocketIOPacket('3'); // PONG
decodeSocketIOPacket('40/chat'); // CONNECT to /chat namespace
/*
Output:
Raw packet: 0
Engine.io type: message
Socket.io data: 0
Socket.io type: CONNECT
---
Raw packet: 2["message",{"text":"hello"}]
Engine.io type: message
Socket.io data: 2["message",{"text":"hello"}]
Socket.io type: EVENT
Event name: message
Event data: { text: 'hello' }
---
*/// ✅ Example 3: See Engine.io session details
const io = require('socket.io')(3000);
io.on('connection', (socket) => {
const engine = socket.conn;
console.log('\n=== Engine.io Session Details ===');
console.log('Session ID:', engine.id);
console.log('Transport:', engine.transport.name);
console.log('Ready state:', engine.readyState);
console.log('Remote address:', engine.request.connection.remoteAddress);
console.log('Upgraded:', engine.upgraded);
// Transport details
console.log('\n=== Transport Details ===');
console.log('Name:', engine.transport.name);
console.log('Writable:', engine.transport.writable);
console.log('Supports binary:', engine.transport.supportsBinary);
// Ping/Pong
engine.on('ping', () => {
console.log('📍 Ping sent');
});
engine.on('pong', () => {
console.log('📍 Pong received (latency: ' + engine.pingInterval + 'ms)');
});
// Packets
let packetCount = 0;
engine.on('packet', (packet) => {
packetCount++;
console.log(`Packet #${packetCount}:`, packet.type, packet.data?.slice(0, 50));
});
// Upgrade
engine.on('upgrade', (transport) => {
console.log('\n⬆️ Transport upgraded!');
console.log('From: polling → To:', transport.name);
});
// Close
engine.on('close', (reason) => {
console.log('\n❌ Engine.io closed:', reason);
console.log('Total packets:', packetCount);
});
});
/*
Output when client connects:
=== Engine.io Session Details ===
Session ID: abc123xyz
Transport: polling
Ready state: open
Remote address: ::1
Upgraded: false
=== Transport Details ===
Name: polling
Writable: true
Supports binary: false
📍 Ping sent
📍 Pong received (latency: 25000ms)
Packet #1: message 0
Packet #2: message 2["my-event",...]
⬆️ Transport upgraded!
From: polling → To: websocket
=== Transport Details ===
Name: websocket
Writable: true
Supports binary: true
*/Transport Mechanisms
Socket.io কিভাবে different transports ব্যবহার করে।
1. Available Transports
/*
Socket.io supports 2 transports:
1. WebSocket
✅ Real-time, bi-directional
✅ Low latency
✅ Efficient
❌ Might be blocked by proxies/firewalls
2. HTTP Long-polling
✅ Works everywhere (pure HTTP)
✅ Firewall-friendly
❌ Higher latency
❌ More overhead
*/2. Transport Selection Process
/*
Connection Flow:
┌─────────────────────────────────┐
│ 1. Start with Polling │
├─────────────────────────────────┤
│ Client: GET /socket.io/?EIO=4...│
│ &transport=polling │
│ │
│ Server: {"sid":"abc123",...} │
│ │
│ Why polling first? │
│ - Works everywhere │
│ - Establishes session │
│ - Gets session ID │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 2. Try WebSocket Upgrade │
├─────────────────────────────────┤
│ Client: WS /socket.io/?EIO=4... │
│ &transport=websocket │
│ &sid=abc123 │
│ │
│ If successful: │
│ → Close polling │
│ → Use WebSocket │
│ │
│ If failed: │
│ → Keep using polling │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 3. Final Transport │
├─────────────────────────────────┤
│ WebSocket (preferred) │
│ OR │
│ Polling (fallback) │
└─────────────────────────────────┘
*/3. Polling Mechanism
/*
HTTP Long-polling কিভাবে কাজ করে:
Traditional Polling:
┌─────────────────────────────────┐
│ Client → Server: Any data? │
│ Server → Client: No │
│ [Wait 1 second] │
│ Client → Server: Any data? │
│ Server → Client: No │
│ [Repeat...] │
└─────────────────────────────────┘
Wasteful!
Long-polling (Engine.io):
┌─────────────────────────────────┐
│ Client → Server: Any data? │
│ [Server holds connection open] │
│ [Server waits for data...] │
│ [Data arrives!] │
│ Server → Client: Yes, here! │
│ Client → Server: Any data? │
│ [Immediately reconnects] │
└─────────────────────────────────┘
More efficient!
Implementation:
*/
// Client side (simplified)
async function poll() {
const response = await fetch(`/socket.io/?EIO=4&transport=polling&sid=${sessionId}`);
const data = await response.text();
processData(data);
// Immediately poll again
poll();
}
// Server side
app.get('/socket.io/', (req, res) => {
const sid = req.query.sid;
const session = sessions.get(sid);
// Hold request until data available
session.waitForData().then(data => {
res.send(data);
});
// Or timeout after 60 seconds
setTimeout(() => {
res.send(''); // Empty response
}, 60000);
});4. Transport Upgrade
/*
Polling → WebSocket upgrade:
Step 1: Establish polling session
┌──────────────────────────────┐
│ POST /socket.io/?transport= │
│ polling&EIO=4 │
│ │
│ Response: {"sid":"abc"} │
└──────────────────────────────┘
Step 2: Probe WebSocket
┌──────────────────────────────┐
│ WS /socket.io/?transport= │
│ websocket&sid=abc │
│ │
│ Send: "2probe" (ping) │
│ Expect: "3probe" (pong) │
└──────────────────────────────┘
Step 3: Confirm upgrade
┌──────────────────────────────┐
│ If probe successful: │
│ Send: "5" (upgrade) │
│ Server: Pause polling │
│ Client: Close polling │
│ Client: Use WebSocket │
└──────────────────────────────┘
Step 4: Resume communication
┌──────────────────────────────┐
│ All future messages via WS │
└──────────────────────────────┘
Why this complexity?
- Ensures WebSocket truly works
- No data loss during switch
- Graceful fallback if upgrade fails
*/📝 Hands-on Example: Observe Transport Upgrade Live
// ✅ Example 1: Monitor upgrade process in detail
// server.js
const io = require('socket.io')(3000, {
// Enable all logs
transports: ['polling', 'websocket'],
allowUpgrades: true
});
io.engine.on('connection', (socket) => {
console.log('\n=== New Engine.io Connection ===');
console.log('ID:', socket.id);
console.log('Initial transport:', socket.transport.name);
let step = 1;
socket.on('upgrading', (transport) => {
console.log(`\n[Step ${step++}] 🔄 Upgrading...`);
console.log('New transport:', transport.name);
});
socket.on('upgrade', (transport) => {
console.log(`\n[Step ${step++}] ✅ Upgrade complete!`);
console.log('Final transport:', transport.name);
console.log('Old transport closed');
});
socket.on('packet', (packet) => {
if (packet.type === 'ping') {
console.log('📍 Ping via', socket.transport.name);
}
if (packet.data === '2probe') {
console.log(`\n[Step ${step++}] 🔍 Probe request received`);
}
if (packet.data === '3probe') {
console.log(`[Step ${step++}] 📮 Probe response sent`);
}
if (packet.data === '5') {
console.log(`[Step ${step++}] 🚀 Upgrade confirmed`);
}
});
});
io.on('connection', (socket) => {
console.log('\n=== Socket.io Connected ===');
console.log('Transport:', socket.conn.transport.name);
// Send message every 2 seconds
const interval = setInterval(() => {
socket.emit('server-time', {
time: new Date().toISOString(),
transport: socket.conn.transport.name
});
}, 2000);
socket.on('disconnect', () => {
clearInterval(interval);
});
});
console.log('Server running on http://localhost:3000');
/*
Output sequence:
=== New Engine.io Connection ===
ID: abc123
Initial transport: polling
[Step 1] 🔍 Probe request received
[Step 2] 📮 Probe response sent
[Step 3] 🚀 Upgrade confirmed
[Step 4] 🔄 Upgrading...
New transport: websocket
[Step 5] ✅ Upgrade complete!
Final transport: websocket
Old transport closed
=== Socket.io Connected ===
Transport: websocket
📍 Ping via websocket
📍 Ping via websocket
*/// ✅ Example 2: Client-side upgrade monitoring
// client.html
/*
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io({
transports: ['polling', 'websocket']
});
let step = 1;
const log = [];
function addLog(message) {
const timestamp = new Date().toISOString().split('T')[1];
const entry = `[${timestamp}] ${message}`;
log.push(entry);
console.log(entry);
}
// Monitor Engine.io events
const engine = socket.io.engine;
engine.on('packet', (packet) => {
addLog(`📦 Packet: ${packet.type} ${packet.data || ''}`);
});
engine.on('packetCreate', (packet) => {
addLog(`📤 Sending: ${packet.type} ${packet.data || ''}`);
});
engine.on('upgrading', (transport) => {
addLog(`[Step ${step++}] 🔄 Client: Starting upgrade to ${transport.name}`);
});
engine.on('upgrade', (transport) => {
addLog(`[Step ${step++}] ✅ Client: Upgraded to ${transport.name}`);
});
engine.on('upgradeError', (error) => {
addLog(`❌ Upgrade failed: ${error.message}`);
});
socket.on('connect', () => {
addLog(`✅ Connected via ${engine.transport.name}`);
});
socket.on('server-time', (data) => {
addLog(`⏰ Server time via ${data.transport}: ${data.time}`);
});
// Export log
setTimeout(() => {
console.log('\n=== Complete Log ===');
log.forEach(l => console.log(l));
}, 10000);
</script>
*/
/*
Client console output:
[10:30:00.123] 📦 Packet: open {"sid":"abc123"...}
[10:30:00.150] ✅ Connected via polling
[10:30:00.200] 📤 Sending: message 0
[10:30:00.250] [Step 1] 🔄 Client: Starting upgrade to websocket
[10:30:00.300] 📤 Sending: ping 2probe
[10:30:00.350] 📦 Packet: pong 3probe
[10:30:00.400] [Step 2] ✅ Client: Upgraded to websocket
[10:30:00.450] 📤 Sending: message 5
[10:30:02.500] ⏰ Server time via websocket: 2026-01-18T10:30:02.500Z
[10:30:04.500] ⏰ Server time via websocket: 2026-01-18T10:30:04.500Z
*/5. Force Transport
// ===== CLIENT =====
// Only use WebSocket (no fallback)
const socket = io({
transports: ['websocket']
});
// Only use Polling (testing/debugging)
const socket = io({
transports: ['polling']
});
// Try WebSocket first, fallback to polling
const socket = io({
transports: ['websocket', 'polling']
});
// Default: Start with polling, upgrade to WebSocket
const socket = io(); // Same as ['polling', 'websocket']
/*
When to force WebSocket only?
✅ Internal network (no proxy issues)
✅ Mobile apps (guaranteed support)
✅ Performance critical
✅ Want to avoid upgrade overhead
When to allow polling fallback?
✅ Public internet
✅ Corporate networks
✅ Maximum compatibility
✅ Unknown client environment
*/6. Transport Events
// ===== CLIENT =====
socket.on('connect', () => {
console.log('Connected!');
console.log('Transport:', socket.io.engine.transport.name);
// "websocket" or "polling"
});
// Listen for transport upgrade
socket.io.engine.on('upgrade', (transport) => {
console.log('Upgraded to:', transport.name);
// polling → websocket
});
// Listen for transport close
socket.io.engine.on('close', (reason) => {
console.log('Transport closed:', reason);
});
// Monitor active transport
socket.io.engine.on('packet', (packet) => {
console.log('Packet:', packet.type, 'via', socket.io.engine.transport.name);
});
/*
Typical upgrade sequence:
1. connect (transport: polling)
2. upgrade (transport: websocket)
3. [All future packets via websocket]
If upgrade fails:
1. connect (transport: polling)
2. [Stays on polling forever]
*/Socket.io Setup & Basics
Socket.io হলো সবচেয়ে জনপ্রিয় WebSocket library যা অনেক features provide করে।
1. Installation & Basic Setup
# Server-side
npm install socket.io
# Client-side (if using separate frontend)
npm install socket.io-client// server.js
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server);
app.use(express.static('public'));
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Socket.io Client</title>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<h1>Socket.io Demo</h1>
<div id="status">Connecting...</div>
<script>
const socket = io();
socket.on('connect', () => {
document.getElementById('status').textContent = 'Connected: ' + socket.id;
});
socket.on('disconnect', () => {
document.getElementById('status').textContent = 'Disconnected';
});
</script>
</body>
</html>2. CORS Configuration
// server.js
const { Server } = require('socket.io');
const io = new Server(server, {
cors: {
origin: ['http://localhost:5173', 'https://myapp.com'],
methods: ['GET', 'POST'],
credentials: true
}
});
// Or allow all origins (Development only!)
const io = new Server(server, {
cors: {
origin: '*'
}
});3. Socket.io Options
// ✅ Advanced Socket.io configuration
const io = new Server(server, {
// Connection options
pingTimeout: 60000, // How long to wait for ping response
pingInterval: 25000, // How often to ping clients
upgradeTimeout: 10000, // Time to wait for upgrade
maxHttpBufferSize: 1e6, // Max message size (1MB)
// Transport options
transports: ['websocket', 'polling'], // Preferred transports
// CORS
cors: {
origin: 'http://localhost:5173',
credentials: true
},
// Path
path: '/socket.io/',
// Serialization
parser: undefined // Custom parser if needed
});Events & Event Handling
Socket.io events হলো real-time communication এর core।
1. Basic Events
// ===== SERVER =====
io.on('connection', (socket) => {
console.log('Connected:', socket.id);
// Listen for custom events
socket.on('chat-message', (data) => {
console.log('Chat message:', data);
// Send acknowledgment
socket.emit('message-received', {
id: data.id,
timestamp: Date.now()
});
});
socket.on('typing', (isTyping) => {
console.log('User typing:', isTyping);
});
socket.on('disconnect', (reason) => {
console.log('Disconnected:', reason);
});
});
// ===== CLIENT =====
const socket = io();
// Send events
socket.emit('chat-message', {
id: 123,
text: 'Hello!',
user: 'John'
});
socket.emit('typing', true);
// Listen for events
socket.on('message-received', (data) => {
console.log('Message delivered:', data);
});2. Acknowledgments (Callbacks)
// ===== SERVER =====
socket.on('save-data', (data, callback) => {
// Process data
saveToDatabase(data)
.then(() => {
// Success callback
callback({
status: 'success',
message: 'Data saved'
});
})
.catch((error) => {
// Error callback
callback({
status: 'error',
message: error.message
});
});
});
// ===== CLIENT =====
socket.emit('save-data', { name: 'John' }, (response) => {
if (response.status === 'success') {
console.log('Saved successfully');
} else {
console.error('Save failed:', response.message);
}
});
// ✅ With timeout
socket.timeout(5000).emit('save-data', data, (err, response) => {
if (err) {
console.error('Request timeout');
} else {
console.log('Response:', response);
}
});3. Event Object Structure
// ✅ Best practice: Structured events
const EventTypes = {
MESSAGE_SEND: 'message:send',
MESSAGE_RECEIVE: 'message:receive',
MESSAGE_DELETE: 'message:delete',
USER_JOIN: 'user:join',
USER_LEAVE: 'user:leave',
TYPING_START: 'typing:start',
TYPING_STOP: 'typing:stop'
};
// Server
socket.on(EventTypes.MESSAGE_SEND, (data) => {
const message = {
id: generateId(),
text: data.text,
user: socket.user,
timestamp: Date.now()
};
io.emit(EventTypes.MESSAGE_RECEIVE, message);
});
// Client
socket.on(EventTypes.MESSAGE_RECEIVE, (message) => {
displayMessage(message);
});4. Event Validation
// ✅ Validate events before processing
const Joi = require('joi');
const messageSchema = Joi.object({
text: Joi.string().min(1).max(500).required(),
roomId: Joi.string().required()
});
socket.on('send-message', async (data, callback) => {
try {
// Validate
const { error, value } = messageSchema.validate(data);
if (error) {
callback({ error: error.details[0].message });
return;
}
// Process valid data
const message = await processMessage(value);
callback({ success: true, message });
} catch (err) {
callback({ error: 'Internal error' });
}
});Rooms & Namespaces
Rooms এবং Namespaces দিয়ে connections organize করা যায়।
1. Basic Rooms
// ===== SERVER =====
// Join a room
socket.on('join-room', (roomId) => {
socket.join(roomId);
console.log(`Socket ${socket.id} joined room ${roomId}`);
// Notify others in the room
socket.to(roomId).emit('user-joined', {
userId: socket.id,
roomId: roomId
});
// Send to the joiner
socket.emit('joined-room', {
roomId: roomId,
users: getRoomUsers(roomId)
});
});
// Leave a room
socket.on('leave-room', (roomId) => {
socket.leave(roomId);
socket.to(roomId).emit('user-left', {
userId: socket.id
});
});
// Send message to room
socket.on('room-message', (data) => {
io.to(data.roomId).emit('message', {
text: data.text,
user: socket.id,
timestamp: Date.now()
});
});
// ===== CLIENT =====
const socket = io();
// Join room
socket.emit('join-room', 'room-123');
socket.on('joined-room', (data) => {
console.log('Joined room:', data.roomId);
console.log('Users in room:', data.users);
});
socket.on('user-joined', (data) => {
console.log('User joined:', data.userId);
});
// Send message to room
function sendRoomMessage(text) {
socket.emit('room-message', {
roomId: 'room-123',
text: text
});
}2. Multiple Rooms
// ✅ User can be in multiple rooms
socket.on('join-channels', (channels) => {
channels.forEach(channel => {
socket.join(channel);
});
console.log('User rooms:', socket.rooms);
// Set { socket.id, 'room1', 'room2', 'room3' }
});
// Leave all rooms except own room
socket.on('leave-all-rooms', () => {
socket.rooms.forEach(room => {
if (room !== socket.id) {
socket.leave(room);
}
});
});
// Get all rooms
function getAllRooms() {
return io.sockets.adapter.rooms;
}
// Get users in a room
function getRoomUsers(roomId) {
const room = io.sockets.adapter.rooms.get(roomId);
return room ? Array.from(room) : [];
}3. Namespaces
// ===== SERVER =====
// Default namespace
io.on('connection', (socket) => {
console.log('Connected to default namespace');
});
// Chat namespace
const chatNamespace = io.of('/chat');
chatNamespace.on('connection', (socket) => {
console.log('Connected to chat namespace:', socket.id);
socket.on('message', (data) => {
chatNamespace.emit('new-message', data);
});
});
// Admin namespace
const adminNamespace = io.of('/admin');
adminNamespace.use((socket, next) => {
// Authentication middleware
if (socket.handshake.auth.token === 'admin-token') {
next();
} else {
next(new Error('Unauthorized'));
}
});
adminNamespace.on('connection', (socket) => {
console.log('Admin connected:', socket.id);
socket.on('broadcast', (data) => {
// Send to all namespaces
io.emit('announcement', data);
chatNamespace.emit('announcement', data);
});
});
// ===== CLIENT =====
// Connect to default namespace
const socket = io();
// Connect to chat namespace
const chatSocket = io('/chat');
chatSocket.on('connect', () => {
console.log('Connected to chat');
});
chatSocket.emit('message', { text: 'Hello!' });
// Connect to admin namespace with auth
const adminSocket = io('/admin', {
auth: {
token: 'admin-token'
}
});
adminSocket.on('connect_error', (error) => {
console.error('Auth failed:', error.message);
});4. Dynamic Namespaces
// ✅ Create namespaces dynamically
io.of(/^\/team-\w+$/).on('connection', (socket) => {
const namespace = socket.nsp;
console.log('Connected to:', namespace.name);
socket.on('message', (data) => {
namespace.emit('new-message', {
team: namespace.name,
...data
});
});
});
// Client connects to: /team-alpha, /team-beta, etc.
const teamSocket = io('/team-alpha');Broadcasting Patterns
বিভিন্ন broadcasting scenarios।
1. Broadcasting Options
// ===== SERVER =====
socket.on('message', (data) => {
// 1. Send to everyone including sender
io.emit('message', data);
// 2. Send to everyone except sender
socket.broadcast.emit('message', data);
// 3. Send to specific room
io.to('room-1').emit('message', data);
// 4. Send to room except sender
socket.to('room-1').emit('message', data);
// 5. Send to multiple rooms
socket.to('room-1').to('room-2').emit('message', data);
// 6. Send to specific socket
io.to(socketId).emit('message', data);
// 7. Send to all in namespace
io.of('/chat').emit('message', data);
// 8. Send to all except specific rooms
socket.broadcast.except('room-1').emit('message', data);
});2. Targeted Broadcasting
// ✅ Chat room with private messages
const users = new Map(); // socketId -> userData
io.on('connection', (socket) => {
// Register user
socket.on('register', (userData) => {
users.set(socket.id, userData);
socket.userId = userData.id;
});
// Public message to room
socket.on('room-message', (data) => {
io.to(data.roomId).emit('message', {
type: 'public',
from: users.get(socket.id),
text: data.text,
timestamp: Date.now()
});
});
// Private message to specific user
socket.on('private-message', (data) => {
const recipientSocketId = findSocketByUserId(data.toUserId);
if (recipientSocketId) {
// Send to recipient
io.to(recipientSocketId).emit('message', {
type: 'private',
from: users.get(socket.id),
text: data.text,
timestamp: Date.now()
});
// Send confirmation to sender
socket.emit('message-sent', {
to: data.toUserId,
status: 'delivered'
});
}
});
});
function findSocketByUserId(userId) {
for (const [socketId, userData] of users) {
if (userData.id === userId) {
return socketId;
}
}
return null;
}3. Conditional Broadcasting
// ✅ Broadcast based on conditions
socket.on('update-status', (status) => {
const user = users.get(socket.id);
user.status = status;
// Broadcast to friends only
const friendSocketIds = getFriendSocketIds(user.id);
friendSocketIds.forEach(friendSocketId => {
io.to(friendSocketId).emit('friend-status-change', {
userId: user.id,
status: status
});
});
});
// ✅ Broadcast with filters
socket.on('notification', (data) => {
// Send to users with specific roles
io.fetchSockets().then(sockets => {
sockets.forEach(s => {
const user = users.get(s.id);
if (user && user.role === 'admin') {
s.emit('notification', data);
}
});
});
});Authentication & Authorization
Socket.io connections secure করা।
1. Token-based Authentication
// ===== SERVER =====
const jwt = require('jsonwebtoken');
// Middleware for authentication
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication error'));
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
socket.userId = decoded.userId;
socket.user = decoded;
next();
} catch (err) {
next(new Error('Invalid token'));
}
});
io.on('connection', (socket) => {
console.log('Authenticated user:', socket.userId);
// User is authenticated, proceed
socket.join(`user:${socket.userId}`);
});
// ===== CLIENT =====
const token = localStorage.getItem('token');
const socket = io({
auth: {
token: token
}
});
socket.on('connect_error', (error) => {
if (error.message === 'Authentication error') {
console.error('Please login first');
// Redirect to login
}
});2. Session-based Authentication
// ===== SERVER =====
const session = require('express-session');
const sharedsession = require('express-socket.io-session');
// Express session
const sessionMiddleware = session({
secret: 'your-secret',
resave: false,
saveUninitialized: false
});
app.use(sessionMiddleware);
// Share session with Socket.io
io.use(sharedsession(sessionMiddleware, {
autoSave: true
}));
io.on('connection', (socket) => {
const session = socket.handshake.session;
if (!session.userId) {
socket.disconnect();
return;
}
console.log('User from session:', session.userId);
});3. Role-based Authorization
// ✅ Check permissions for actions
const permissions = {
admin: ['delete', 'ban', 'broadcast'],
moderator: ['delete', 'mute'],
user: ['send-message']
};
function hasPermission(socket, action) {
const userRole = socket.user.role;
return permissions[userRole]?.includes(action);
}
socket.on('delete-message', (messageId) => {
if (!hasPermission(socket, 'delete')) {
socket.emit('error', {
message: 'Permission denied'
});
return;
}
// Proceed with deletion
deleteMessage(messageId);
});
socket.on('ban-user', (userId) => {
if (!hasPermission(socket, 'ban')) {
socket.emit('error', {
message: 'Only admins can ban users'
});
return;
}
banUser(userId);
});4. Rate Limiting
// ✅ Prevent spam and abuse
const rateLimit = new Map();
function checkRateLimit(socketId, limit = 10, window = 60000) {
const now = Date.now();
const userLimit = rateLimit.get(socketId) || { count: 0, resetTime: now + window };
if (now > userLimit.resetTime) {
userLimit.count = 0;
userLimit.resetTime = now + window;
}
userLimit.count++;
rateLimit.set(socketId, userLimit);
return userLimit.count <= limit;
}
socket.on('send-message', (data) => {
if (!checkRateLimit(socket.id, 10, 60000)) {
socket.emit('error', {
message: 'Too many messages. Please slow down.'
});
return;
}
// Process message
broadcastMessage(data);
});
// Cleanup old entries periodically
setInterval(() => {
const now = Date.now();
for (const [socketId, limit] of rateLimit) {
if (now > limit.resetTime) {
rateLimit.delete(socketId);
}
}
}, 60000);SSE Fundamentals
Server-Sent Events (SSE) একটা simple way server থেকে client এ data push করার।
1. Basic SSE Server
// server.js
const express = require('express');
const app = express();
app.use(express.static('public'));
app.get('/api/events', (req, res) => {
// Set headers for SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Send initial message
res.write('data: Connected to SSE\n\n');
// Send updates every 2 seconds
const intervalId = setInterval(() => {
const data = {
timestamp: new Date().toISOString(),
value: Math.random()
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 2000);
// Cleanup on client disconnect
req.on('close', () => {
clearInterval(intervalId);
console.log('Client disconnected');
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});2. SSE Client
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>SSE Demo</title>
</head>
<body>
<h1>Server-Sent Events</h1>
<div id="status">Disconnected</div>
<div id="data"></div>
<script>
const eventSource = new EventSource('/api/events');
const status = document.getElementById('status');
const dataDiv = document.getElementById('data');
eventSource.onopen = () => {
status.textContent = 'Connected';
status.style.color = 'green';
};
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
const p = document.createElement('p');
p.textContent = `${data.timestamp}: ${data.value.toFixed(4)}`;
dataDiv.appendChild(p);
// Keep only last 10 messages
if (dataDiv.children.length > 10) {
dataDiv.removeChild(dataDiv.firstChild);
}
};
eventSource.onerror = (error) => {
console.error('SSE error:', error);
status.textContent = 'Error';
status.style.color = 'red';
};
</script>
</body>
</html>3. Named Events
// ===== SERVER =====
app.get('/api/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Send different event types
const sendUpdate = () => {
res.write(`event: update\ndata: ${JSON.stringify({ type: 'update' })}\n\n`);
};
const sendNotification = () => {
res.write(`event: notification\ndata: ${JSON.stringify({ message: 'New notification' })}\n\n`);
};
const sendPing = () => {
res.write(`event: ping\ndata: ${Date.now()}\n\n`);
};
setInterval(sendUpdate, 5000);
setInterval(sendNotification, 10000);
setInterval(sendPing, 30000);
req.on('close', () => {
clearInterval(sendUpdate);
clearInterval(sendNotification);
clearInterval(sendPing);
});
});
// ===== CLIENT =====
const eventSource = new EventSource('/api/events');
// Listen for specific events
eventSource.addEventListener('update', (event) => {
const data = JSON.parse(event.data);
console.log('Update:', data);
});
eventSource.addEventListener('notification', (event) => {
const data = JSON.parse(event.data);
showNotification(data.message);
});
eventSource.addEventListener('ping', (event) => {
console.log('Ping:', event.data);
});4. SSE with Event IDs & Retry
// ===== SERVER =====
let eventId = 0;
app.get('/api/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Get last event ID from client
const lastEventId = req.headers['last-event-id'];
if (lastEventId) {
console.log('Resuming from event:', lastEventId);
// Send missed events
}
// Set retry time (milliseconds)
res.write('retry: 5000\n\n');
const interval = setInterval(() => {
eventId++;
const event = {
id: eventId,
data: { message: `Event ${eventId}` },
timestamp: Date.now()
};
// Send with ID
res.write(`id: ${eventId}\n`);
res.write(`data: ${JSON.stringify(event.data)}\n\n`);
}, 2000);
req.on('close', () => {
clearInterval(interval);
});
});
// ===== CLIENT =====
const eventSource = new EventSource('/api/events');
eventSource.onmessage = (event) => {
console.log('Event ID:', event.lastEventId);
console.log('Data:', event.data);
// If connection drops, browser automatically
// sends 'Last-Event-ID' header on reconnect
};SSE vs WebSockets
দুটোর মধ্যে পার্থক্য এবং কখন কোনটা ব্যবহার করবেন।
1. Feature Comparison
/*
┌──────────────────────┬─────────────┬─────────────┐
│ Feature │ WebSocket │ SSE │
├──────────────────────┼─────────────┼─────────────┤
│ Direction │ Bi-direct │ Server→Client│
│ Protocol │ ws:// │ HTTP │
│ Browser Support │ Excellent │ Excellent │
│ Auto Reconnect │ Manual │ Automatic │
│ Event IDs │ Manual │ Built-in │
│ Text Only │ No (Binary) │ Yes │
│ Compression │ Yes │ Yes │
│ HTTP/2 Multiplexing │ No │ Yes │
│ Firewall Friendly │ Sometimes │ Always │
│ Complexity │ Medium │ Simple │
├──────────────────────┼─────────────┼─────────────┤
│ Best For │ Chat, Games │ Feeds, News │
└──────────────────────┴─────────────┴─────────────┘
*/2. When to Use What
// ✅ Use WebSocket when:
// - Bi-directional communication needed
// - Real-time chat, gaming
// - Low latency required
// - Binary data (images, files)
const chatSocket = io();
// ✅ Use SSE when:
// - Only server-to-client updates
// - News feeds, notifications
// - Stock tickers, sports scores
// - Simple implementation needed
const feedSource = new EventSource('/api/feed');3. Practical Example - Live Score Updates
// ===== Using SSE (Better for this case) =====
// Server
app.get('/api/scores', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const sendScore = async () => {
const scores = await getLatestScores();
res.write(`data: ${JSON.stringify(scores)}\n\n`);
};
const interval = setInterval(sendScore, 5000);
req.on('close', () => clearInterval(interval));
});
// Client
const scores = new EventSource('/api/scores');
scores.onmessage = (event) => {
const data = JSON.parse(event.data);
updateScoreboard(data);
};
// Auto reconnect on error
scores.onerror = () => {
console.log('Connection lost, reconnecting...');
// Browser automatically reconnects!
};Scalability & Load Balancing
Multiple servers এ Socket.io scale করা।
1. The Problem
// ❌ Problem: With multiple servers
/*
┌─────────────┐ ┌─────────────┐
│ Server 1 │ │ Server 2 │
│ Users: A,B │ │ Users: C,D │
└─────────────┘ └─────────────┘
User A sends message → Only Server 1 users see it
User C won't receive the message (on Server 2)
*/2. Solution: Redis Adapter
npm install @socket.io/redis-adapter redis// server.js
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const app = express();
const server = http.createServer(app);
const io = new Server(server);
// Create Redis clients
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
io.adapter(createAdapter(pubClient, subClient));
console.log('Redis adapter connected');
});
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
socket.on('message', (data) => {
// This will broadcast across ALL servers
io.emit('message', data);
});
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});3. Load Balancer Setup (Nginx)
# nginx.conf
upstream socketio_backend {
# Use IP hash for sticky sessions
ip_hash;
server localhost:3000;
server localhost:3001;
server localhost:3002;
}
server {
listen 80;
location /socket.io/ {
proxy_pass http://socketio_backend;
proxy_http_version 1.1;
# WebSocket headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Timeouts
proxy_connect_timeout 7d;
proxy_send_timeout 7d;
proxy_read_timeout 7d;
}
}4. Sticky Sessions
// ✅ Enable sticky sessions in Socket.io
const io = new Server(server, {
transports: ['websocket', 'polling'],
allowEIO3: true, // Enable Engine.IO v3 for sticky sessions
cookie: {
name: 'io',
path: '/',
httpOnly: true,
sameSite: 'lax'
}
});
// With PM2
/*
{
"apps": [{
"name": "socket-server",
"script": "./server.js",
"instances": 4,
"exec_mode": "cluster"
}]
}
*/Redis Adapter for Multi-server
Redis adapter দিয়ে multiple servers sync করা।
1. Complete Setup
// server.js
const { Server } = require('socket.io');
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const io = new Server(server, {
adapter: createAdapter(pubClient, subClient)
});
// Track server info
const SERVER_ID = process.env.SERVER_ID || 'server-1';
io.on('connection', (socket) => {
console.log(`[${SERVER_ID}] User connected:`, socket.id);
// Join user to their personal room
socket.join(`user:${socket.userId}`);
// Broadcast to all servers
socket.on('global-message', (data) => {
io.emit('message', {
...data,
serverId: SERVER_ID
});
});
// Send to specific user across servers
socket.on('private-message', (data) => {
io.to(`user:${data.toUserId}`).emit('message', {
from: socket.userId,
text: data.text,
serverId: SERVER_ID
});
});
});2. Redis Pub/Sub Pattern
// ✅ Manual Redis pub/sub for custom logic
const redis = require('redis');
const publisher = redis.createClient();
const subscriber = redis.createClient();
await publisher.connect();
await subscriber.connect();
// Subscribe to channels
await subscriber.subscribe('notifications', (message) => {
const data = JSON.parse(message);
// Broadcast to local clients
io.emit('notification', data);
});
// Publish from any server
socket.on('send-notification', async (data) => {
await publisher.publish('notifications', JSON.stringify({
message: data.message,
serverId: SERVER_ID,
timestamp: Date.now()
}));
});3. Cluster Communication
// ✅ Server-to-server messages
io.of('/').adapter.on('create-room', (room) => {
console.log(`Room ${room} was created`);
});
io.of('/').adapter.on('delete-room', (room) => {
console.log(`Room ${room} was deleted`);
});
io.of('/').adapter.on('join-room', (room, id) => {
console.log(`Socket ${id} joined room ${room}`);
});
io.of('/').adapter.on('leave-room', (room, id) => {
console.log(`Socket ${id} left room ${room}`);
});
// Get all sockets across all servers
const sockets = await io.fetchSockets();
console.log(`Total connected: ${sockets.length}`);
// Get sockets in a room across all servers
const roomSockets = await io.in('room-1').fetchSockets();
console.log(`Users in room-1: ${roomSockets.length}`);Performance Optimization
Socket.io applications এর performance optimize করা।
1. Connection Optimization
// ✅ Optimize connection settings
const io = new Server(server, {
// Only use WebSocket (skip polling)
transports: ['websocket'],
// Reduce ping/pong overhead
pingInterval: 30000, // 30 seconds
pingTimeout: 60000, // 60 seconds
// Compression
perMessageDeflate: {
threshold: 1024 // Only compress messages > 1KB
},
// Max payload size
maxHttpBufferSize: 1e6, // 1MB
// Disable upgrade
allowUpgrades: false
});2. Message Batching
// ✅ Batch multiple messages
class MessageBatcher {
constructor(io, interval = 100) {
this.io = io;
this.interval = interval;
this.batches = new Map();
setInterval(() => this.flush(), interval);
}
add(room, event, data) {
const key = `${room}:${event}`;
if (!this.batches.has(key)) {
this.batches.set(key, []);
}
this.batches.get(key).push(data);
}
flush() {
for (const [key, messages] of this.batches) {
if (messages.length === 0) continue;
const [room, event] = key.split(':');
this.io.to(room).emit(event, messages);
messages.length = 0;
}
}
}
// Usage
const batcher = new MessageBatcher(io, 100);
socket.on('position-update', (data) => {
// Instead of emitting immediately
// io.to('game-room').emit('player-move', data);
// Batch it
batcher.add('game-room', 'player-moves', data);
// Will send all moves together every 100ms
});3. Binary Data Optimization
// ✅ Send binary data efficiently
socket.on('send-image', (data) => {
// Convert to Buffer for efficient transmission
const buffer = Buffer.from(data.image, 'base64');
socket.broadcast.emit('image', buffer);
});
// Client
socket.on('image', (buffer) => {
const blob = new Blob([buffer], { type: 'image/jpeg' });
const url = URL.createObjectURL(blob);
displayImage(url);
});4. Memory Management
// ✅ Clean up inactive connections
const activeConnections = new Map();
io.on('connection', (socket) => {
activeConnections.set(socket.id, {
connectedAt: Date.now(),
lastActivity: Date.now()
});
socket.on('activity', () => {
const conn = activeConnections.get(socket.id);
if (conn) {
conn.lastActivity = Date.now();
}
});
socket.on('disconnect', () => {
activeConnections.delete(socket.id);
});
});
// Disconnect inactive users
setInterval(() => {
const now = Date.now();
const timeout = 30 * 60 * 1000; // 30 minutes
for (const [socketId, conn] of activeConnections) {
if (now - conn.lastActivity > timeout) {
const socket = io.sockets.sockets.get(socketId);
if (socket) {
socket.disconnect(true);
}
activeConnections.delete(socketId);
}
}
}, 60000); // Check every minuteSecurity Best Practices
Socket.io applications secure করার উপায়।
1. Input Validation & Sanitization
const Joi = require('joi');
const DOMPurify = require('isomorphic-dompurify');
// Validation schemas
const schemas = {
message: Joi.object({
text: Joi.string().min(1).max(1000).required(),
roomId: Joi.string().alphanum().required()
}),
profile: Joi.object({
name: Joi.string().min(2).max(50).required(),
bio: Joi.string().max(200).optional()
})
};
socket.on('send-message', async (data, callback) => {
// Validate
const { error, value } = schemas.message.validate(data);
if (error) {
callback({ error: error.details[0].message });
return;
}
// Sanitize
const sanitized = {
text: DOMPurify.sanitize(value.text),
roomId: value.roomId
};
// Process
await saveMessage(sanitized);
callback({ success: true });
});2. XSS Protection
// ✅ Prevent XSS attacks
const xss = require('xss');
socket.on('chat-message', (data) => {
// Clean HTML
const cleanText = xss(data.text, {
whiteList: {}, // No HTML tags allowed
stripIgnoreTag: true,
stripIgnoreTagBody: ['script']
});
io.to(data.roomId).emit('message', {
user: socket.user.name,
text: cleanText,
timestamp: Date.now()
});
});3. Rate Limiting Advanced
// ✅ Advanced rate limiting
class RateLimiter {
constructor(options = {}) {
this.limits = options.limits || {
message: { max: 10, window: 60000 },
join: { max: 5, window: 300000 }
};
this.violations = new Map();
}
check(socketId, action) {
const limit = this.limits[action];
if (!limit) return true;
const key = `${socketId}:${action}`;
const now = Date.now();
const record = this.violations.get(key) || {
count: 0,
resetTime: now + limit.window,
violations: 0
};
if (now > record.resetTime) {
record.count = 0;
record.resetTime = now + limit.window;
}
record.count++;
if (record.count > limit.max) {
record.violations++;
// Ban after 3 violations
if (record.violations >= 3) {
return 'banned';
}
this.violations.set(key, record);
return false;
}
this.violations.set(key, record);
return true;
}
}
const limiter = new RateLimiter();
socket.on('send-message', (data) => {
const allowed = limiter.check(socket.id, 'message');
if (allowed === 'banned') {
socket.emit('error', { message: 'You have been banned' });
socket.disconnect(true);
return;
}
if (!allowed) {
socket.emit('error', { message: 'Rate limit exceeded' });
return;
}
// Process message
});4. CORS Security
// ✅ Strict CORS configuration
const io = new Server(server, {
cors: {
origin: (origin, callback) => {
const allowedOrigins = [
'https://myapp.com',
'https://www.myapp.com'
];
if (!origin) {
// Allow requests with no origin (mobile apps, curl, etc.)
callback(null, true);
return;
}
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST'],
credentials: true,
allowedHeaders: ['Authorization']
}
});5. SQL/NoSQL Injection Prevention
// ✅ Prevent injection attacks
socket.on('search-users', async (query, callback) => {
// BAD: String concatenation
// const users = await db.query(`SELECT * FROM users WHERE name = '${query}'`);
// GOOD: Parameterized query
const users = await db.query(
'SELECT * FROM users WHERE name = $1',
[query]
);
callback({ users });
});
// MongoDB with Mongoose (safe by default)
socket.on('find-post', async (data, callback) => {
// Safe - Mongoose escapes
const post = await Post.findOne({ title: data.title });
callback({ post });
});Error Handling & Reconnection
Errors handle এবং automatic reconnection।
1. Client-side Error Handling
// ===== CLIENT =====
const socket = io({
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
randomizationFactor: 0.5
});
// Connection events
socket.on('connect', () => {
console.log('Connected:', socket.id);
updateStatus('Connected');
});
socket.on('disconnect', (reason) => {
console.log('Disconnected:', reason);
updateStatus('Disconnected');
if (reason === 'io server disconnect') {
// Server forced disconnect, manually reconnect
socket.connect();
}
});
socket.on('connect_error', (error) => {
console.error('Connection error:', error.message);
updateStatus('Connection error');
});
socket.on('reconnect', (attemptNumber) => {
console.log('Reconnected after', attemptNumber, 'attempts');
updateStatus('Reconnected');
});
socket.on('reconnect_attempt', (attemptNumber) => {
console.log('Reconnection attempt:', attemptNumber);
updateStatus(`Reconnecting... (${attemptNumber})`);
});
socket.on('reconnect_failed', () => {
console.error('Reconnection failed');
updateStatus('Connection failed');
});
socket.on('error', (error) => {
console.error('Socket error:', error);
});2. Server-side Error Handling
// ===== SERVER =====
// Global error handler
io.engine.on('connection_error', (err) => {
console.error('Connection error:', {
code: err.code,
message: err.message,
context: err.context
});
});
io.on('connection', (socket) => {
// Socket-specific error handler
socket.on('error', (error) => {
console.error('Socket error:', socket.id, error);
});
// Catch-all for unhandled events
socket.onAny((eventName, ...args) => {
console.log('Event:', eventName, 'Args:', args);
});
// Validation error wrapper
socket.use(([event, ...args], next) => {
try {
// Validate event
if (typeof event !== 'string') {
throw new Error('Invalid event type');
}
next();
} catch (err) {
next(err);
}
});
});3. Graceful Degradation
// ✅ Handle connection issues gracefully
class ConnectionManager {
constructor(socket) {
this.socket = socket;
this.messageQueue = [];
this.isConnected = false;
socket.on('connect', () => {
this.isConnected = true;
this.flushQueue();
});
socket.on('disconnect', () => {
this.isConnected = false;
});
}
send(event, data) {
if (this.isConnected) {
this.socket.emit(event, data);
} else {
// Queue messages while offline
this.messageQueue.push({ event, data });
// Limit queue size
if (this.messageQueue.length > 100) {
this.messageQueue.shift();
}
}
}
flushQueue() {
while (this.messageQueue.length > 0) {
const { event, data } = this.messageQueue.shift();
this.socket.emit(event, data);
}
}
}
// Usage
const manager = new ConnectionManager(socket);
// Always works, queues if offline
manager.send('message', { text: 'Hello' });4. Heartbeat & Health Check
// ===== SERVER =====
const HEARTBEAT_INTERVAL = 30000;
const HEARTBEAT_TIMEOUT = 60000;
io.on('connection', (socket) => {
socket.isAlive = true;
socket.lastHeartbeat = Date.now();
socket.on('pong', () => {
socket.isAlive = true;
socket.lastHeartbeat = Date.now();
});
const heartbeat = setInterval(() => {
if (!socket.isAlive) {
console.log('Client not responding:', socket.id);
socket.disconnect(true);
clearInterval(heartbeat);
return;
}
socket.isAlive = false;
socket.emit('ping');
}, HEARTBEAT_INTERVAL);
socket.on('disconnect', () => {
clearInterval(heartbeat);
});
});
// ===== CLIENT =====
socket.on('ping', () => {
socket.emit('pong');
});Production Deployment Patterns
Production environment এ Socket.io deploy করার best practices।
1. Complete Production Server
// server.js
require('dotenv').config();
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const helmet = require('helmet');
const cors = require('cors');
const compression = require('compression');
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const app = express();
const server = http.createServer(app);
// Security
app.use(helmet());
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
credentials: true
}));
// Compression
app.use(compression());
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
uptime: process.uptime(),
connections: io.engine.clientsCount,
memory: process.memoryUsage(),
timestamp: Date.now()
});
});
// Socket.io with Redis
const io = new Server(server, {
transports: ['websocket'],
cors: {
origin: process.env.ALLOWED_ORIGINS?.split(','),
credentials: true
},
pingTimeout: 60000,
pingInterval: 25000
});
// Setup Redis adapter
if (process.env.REDIS_URL) {
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()])
.then(() => {
io.adapter(createAdapter(pubClient, subClient));
console.log('✅ Redis adapter connected');
})
.catch(err => {
console.error('❌ Redis adapter error:', err);
});
}
// Authentication middleware
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
if (!token) {
throw new Error('Authentication required');
}
const user = await verifyToken(token);
socket.user = user;
next();
} catch (err) {
next(new Error('Authentication failed'));
}
});
// Connection handling
io.on('connection', (socket) => {
console.log('User connected:', socket.user.id);
// Join user's personal room
socket.join(`user:${socket.user.id}`);
// Event handlers
require('./handlers/chatHandler')(io, socket);
require('./handlers/notificationHandler')(io, socket);
socket.on('disconnect', (reason) => {
console.log('User disconnected:', socket.user.id, reason);
});
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, closing server...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
});2. PM2 Ecosystem File
// ecosystem.config.js
module.exports = {
apps: [{
name: 'socket-server',
script: './server.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
REDIS_URL: 'redis://localhost:6379',
ALLOWED_ORIGINS: 'https://myapp.com,https://www.myapp.com'
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
autorestart: true,
max_restarts: 10,
min_uptime: '10s'
}]
};3. Docker Setup
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy source
COPY . .
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node healthcheck.js || exit 1
EXPOSE 3000
CMD ["node", "server.js"]# docker-compose.yml
version: '3.8'
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
socket-server:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- REDIS_URL=redis://redis:6379
- ALLOWED_ORIGINS=https://myapp.com
depends_on:
- redis
restart: unless-stopped
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
volumes:
redis_data:4. Monitoring & Logging
// logger.js
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'socket-server' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
module.exports = logger;
// Usage in server
const logger = require('./logger');
io.on('connection', (socket) => {
logger.info('User connected', {
socketId: socket.id,
userId: socket.user?.id,
ip: socket.handshake.address
});
socket.on('error', (error) => {
logger.error('Socket error', {
socketId: socket.id,
error: error.message,
stack: error.stack
});
});
});5. Metrics & Analytics
// metrics.js
class Metrics {
constructor() {
this.stats = {
connections: 0,
messages: 0,
errors: 0,
rooms: new Map()
};
// Report every minute
setInterval(() => this.report(), 60000);
}
trackConnection(socketId) {
this.stats.connections++;
}
trackMessage(roomId) {
this.stats.messages++;
const count = this.stats.rooms.get(roomId) || 0;
this.stats.rooms.set(roomId, count + 1);
}
trackError() {
this.stats.errors++;
}
report() {
console.log('📊 Metrics:', {
totalConnections: this.stats.connections,
totalMessages: this.stats.messages,
totalErrors: this.stats.errors,
activeRooms: this.stats.rooms.size,
topRooms: Array.from(this.stats.rooms.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
});
// Send to monitoring service (Datadog, New Relic, etc.)
// sendToMonitoring(this.stats);
}
reset() {
this.stats.messages = 0;
this.stats.errors = 0;
}
}
module.exports = new Metrics();এই comprehensive guide এ Real-time Communication এর সব fundamental থেকে production-level advanced concepts cover করা হয়েছে। WebSockets, Socket.io, এবং SSE এর complete knowledge দিয়ে আপনি professional real-time applications তৈরি করতে পারবেন! 🚀