bookworm-smart-assistant/skills/websocket-engineer/references/alternatives.md

392 lines
8.7 KiB
Markdown
Raw Normal View History

# Real-Time Communication Alternatives
## Technology Comparison
| Feature | WebSocket | SSE | Long Polling | HTTP/2 Push | WebRTC |
|---------|-----------|-----|--------------|-------------|--------|
| Bidirectional | Yes | No | Yes | No | Yes |
| Real-time | Yes | Yes | Near | Yes | Yes |
| Browser Support | Excellent | Good | Universal | Good | Good |
| Proxy Issues | Some | Rare | Rare | Some | Some |
| Overhead | Low | Low | High | Medium | Medium |
| Use Case | Chat, games | Feeds, updates | Legacy | Assets | Audio/video |
## Server-Sent Events (SSE)
### When to Use SSE
- One-way server-to-client communication
- Live feeds, notifications, stock tickers
- Automatic reconnection needed
- Simpler than WebSockets
- Better firewall/proxy compatibility
### SSE Server (Node.js)
```javascript
const express = require('express');
const app = express();
app.get('/events', (req, res) => {
// Set SSE headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('Access-Control-Allow-Origin', '*');
// Send initial connection message
res.write('data: {"message": "Connected"}\n\n');
// Send updates every 5 seconds
const intervalId = setInterval(() => {
const data = {
timestamp: Date.now(),
value: Math.random()
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 5000);
// Cleanup on client disconnect
req.on('close', () => {
clearInterval(intervalId);
res.end();
});
});
app.listen(3000);
```
### SSE Client
```javascript
const eventSource = new EventSource('http://localhost:3000/events');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
eventSource.onerror = (error) => {
console.error('SSE error:', error);
// Automatically reconnects
};
// Named events
eventSource.addEventListener('update', (event) => {
console.log('Update:', event.data);
});
// Close connection
eventSource.close();
```
### SSE with Express
```javascript
const express = require('express');
const app = express();
class SSEManager {
constructor() {
this.clients = new Set();
}
addClient(res) {
this.clients.add(res);
}
removeClient(res) {
this.clients.delete(res);
}
broadcast(event, data) {
const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
this.clients.forEach(client => {
client.write(message);
});
}
}
const sseManager = new SSEManager();
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
sseManager.addClient(res);
req.on('close', () => {
sseManager.removeClient(res);
});
});
// Broadcast to all clients
setInterval(() => {
sseManager.broadcast('update', {
timestamp: Date.now(),
activeClients: sseManager.clients.size
});
}, 10000);
app.listen(3000);
```
## Long Polling
### When to Use Long Polling
- Legacy browser support needed
- Firewall/proxy blocks WebSockets
- Very infrequent updates
- Fallback mechanism
### Long Polling Server
```javascript
const express = require('express');
const app = express();
const pendingRequests = new Map();
const messages = [];
app.get('/poll', (req, res) => {
const clientId = req.query.clientId;
// If messages available, send immediately
if (messages.length > 0) {
res.json({ messages });
messages.length = 0; // Clear messages
return;
}
// Hold request until timeout or new message
const timeout = setTimeout(() => {
pendingRequests.delete(clientId);
res.json({ messages: [] });
}, 30000); // 30 second timeout
pendingRequests.set(clientId, { res, timeout });
req.on('close', () => {
clearTimeout(timeout);
pendingRequests.delete(clientId);
});
});
app.post('/send', express.json(), (req, res) => {
messages.push(req.body.message);
// Respond to all pending requests
pendingRequests.forEach(({ res, timeout }, clientId) => {
clearTimeout(timeout);
res.json({ messages });
pendingRequests.delete(clientId);
});
messages.length = 0; // Clear messages
res.json({ success: true });
});
app.listen(3000);
```
### Long Polling Client
```javascript
const clientId = Math.random().toString(36);
async function poll() {
try {
const response = await fetch(
`http://localhost:3000/poll?clientId=${clientId}`,
{ signal: AbortSignal.timeout(35000) }
);
const data = await response.json();
if (data.messages.length > 0) {
console.log('Received messages:', data.messages);
}
// Immediately poll again
poll();
} catch (error) {
console.error('Polling error:', error);
// Retry after delay
setTimeout(poll, 5000);
}
}
poll();
```
## HTTP/2 Server Push (Deprecated)
Note: HTTP/2 Server Push is deprecated and removed from Chrome. Use 103 Early Hints instead.
```javascript
// Example for historical context only
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
});
server.on('stream', (stream, headers) => {
if (headers[':path'] === '/') {
// Push assets before HTML response
stream.pushStream({ ':path': '/style.css' }, (err, pushStream) => {
if (!err) {
pushStream.respondWithFile('style.css');
}
});
stream.respondWithFile('index.html');
}
});
server.listen(3000);
```
## Decision Matrix
### Choose WebSocket When:
- Bidirectional communication needed
- Low latency critical (< 50ms)
- High message frequency (> 1 msg/sec)
- Gaming, chat, collaborative editing
- Binary data transfer
- Custom protocol needed
### Choose SSE When:
- One-way server-to-client only
- Stock tickers, live feeds
- News/notifications
- Simpler implementation preferred
- Better proxy compatibility needed
- Automatic reconnection important
### Choose Long Polling When:
- Legacy browser support required (IE8/9)
- WebSocket blocked by firewall
- Very infrequent updates
- Fallback mechanism only
### Choose HTTP Streaming When:
- Large data transfers
- File uploads with progress
- Video/audio streaming
- One-way data flow
### Choose WebRTC When:
- Peer-to-peer communication
- Audio/video calls
- Screen sharing
- File transfer between peers
- Low latency P2P needed
## Hybrid Approach
```javascript
// Socket.IO with automatic fallback
const io = require('socket.io')(3000, {
transports: ['websocket', 'polling'], // Try WebSocket first
upgrade: true,
allowUpgrades: true
});
io.on('connection', (socket) => {
console.log('Connected via:', socket.conn.transport.name);
socket.conn.on('upgrade', () => {
console.log('Upgraded to:', socket.conn.transport.name);
});
});
```
## Performance Characteristics
### Latency (p99)
- WebSocket: 5-20ms
- SSE: 10-50ms
- Long Polling: 100-500ms
- HTTP/2: 20-100ms
### Throughput (messages/sec)
- WebSocket: 10,000+ per connection
- SSE: 1,000+ per connection
- Long Polling: 1-10 per connection
### Connection Limits (per server)
- WebSocket: 50,000-100,000
- SSE: 50,000-100,000
- Long Polling: 10,000-20,000
### Overhead (per message)
- WebSocket: 2-6 bytes
- SSE: ~20 bytes
- Long Polling: 500-2000 bytes (HTTP headers)
## Migration Path
### From Polling to WebSocket
```javascript
// Step 1: Support both
app.get('/api/messages', (req, res) => {
// Legacy polling endpoint
res.json({ messages: getRecentMessages() });
});
io.on('connection', (socket) => {
// New WebSocket endpoint
socket.on('subscribe', (channel) => {
socket.join(channel);
});
});
// Step 2: Gradually migrate clients
// Step 3: Deprecate polling endpoint
```
### From SSE to WebSocket
```javascript
// SSE provides read-only, add WebSocket for writes
app.get('/events', sseHandler); // Keep for reads
io.on('connection', (socket) => {
socket.on('action', (data) => {
// Handle writes via WebSocket
processAction(data);
});
});
// Eventually migrate reads to WebSocket too
```
## Best Practices
1. Start with simplest solution (SSE for one-way)
2. Use Socket.IO for automatic fallbacks
3. Monitor actual requirements before over-engineering
4. Consider mobile/network constraints
5. Implement graceful degradation
6. Load test before production
7. Have fallback strategy
8. Monitor connection success rates