WebSocket实时通信架构实战
WebSocket实时通信架构实战:从单连接到百万级长连接作者:Crown_22 | AI Agent Hermes Agent 桌面程序开发者前言:为什么HTTP长轮询已经过时了?在实时通信领域,我见过太多项目还在用HTTP长轮询或SSE,然后抱怨"延迟高"、“服务器压力大”。这篇文章不讲WebSocket的基础语法,而是深入探讨如何构建生产级的WebSocket服务。你会学到:WebSocket握手失败的常见原因及解决方案心跳机制的正确实现方式如何处理百万级并发连接消息可靠投递的实现分布式WebSocket集群的架构一、WebSocket握手失败的8种原因❌ 错误1:反向代理配置不当# Nginx配置错误示例 server { listen 80; server_name example.com; location /ws { proxy_pass http://backend:8080; # ❌ 缺少WebSocket必要的头 } } # 客户端连接失败:WebSocket connection to 'ws://example.com/ws' failed✅ 正确的Nginx配置server { listen 80; server_name example.com; location /ws { proxy_pass http://backend:8080; # ✅ WebSocket必要的头 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 超时配置(重要!) proxy_read_timeout 86400s; # 24小时 proxy_send_timeout 86400s; # 缓冲配置 proxy_buffering off; proxy_cache off; } }❌ 错误2:CORS配置问题// 服务端(Node.js + ws库)constWebSocket=require('ws');constwss=newWebSocket.Server({port:8080});// ❌ 没有处理CORSwss.on('connection',(ws,req)={// 跨域连接会被浏览器阻止});✅ 正确处理CORSconstWebSocket=require('ws');consthttp=require('http');constserver=http.createServer((req,res)={// 处理CORS预检请求res.setHeader('Access-Control-Allow-Origin','*');res.setHeader('Access-Control-Allow-Methods','GET, POST, OPTIONS');res.setHeader('Access-Control-Allow-Headers','Content-Type, Authorization');if(req.method==='OPTIONS'){res.writeHead(204);res.end();return;}});constwss=newWebSocket.Server({server});wss.on('connection',(ws,req)={// 验证originconstorigin=req.headers.origin;if(!isAllowedOrigin(origin)){ws.close(1008,'Origin not allowed');return;}console.log('连接建立');});server.listen(8080);❌ 错误3:SSL/TLS配置问题// ❌ 使用ws连接wss服务constws=newWebSocket('ws://secure-server.com/ws');// 浏览器会阻止:Mixed Content错误// ✅ 正确:使用wssconstws=newWebSocket('wss://secure-server.com/ws');# Nginx SSL配置 server { listen 443 ssl; server_name example.com; ssl_certificate /etc/ssl/certs/example.com.crt; ssl_certificate_key /etc/ssl/private/example.com.key; location /ws { proxy_pass http://backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400s; } }二、心跳机制:保活与检测❌ 错误实现:只发不收// 服务端wss.on('connection',(ws)={// ❌ 只发送心跳,不检测客户端响应setInterval(()={ws.send(JSON.stringify({type:'ping'}));},30000);});✅ 正确实现:双向心跳classWebSocketServer{constructor(){this.wss=newWebSocket.Server({port:8080});this.clients=newMap();this.wss.on('connection',(ws)=this.handleConnection(ws));// 定期检测死连接setInterval(()=this.checkAlive(),30000);}handleConnection(ws){constclientId=this.generateId();constclientInfo={ws,alive:true,lastPong:Date.now()};this.clients.set(clientId,clientInfo);// 处理消息ws.on('message',(data)={constmessage=JSON.parse(data);if(message.type==='pong'){clientInfo.alive=true;clientInfo.lastPong=Date.now();return;}this.handleMessage(clientId,message);});// 处理关闭ws.on('close',()={this.clients.delete(clientId);});// 发送欢迎消息ws.send(JSON.stringify({type:'welcome',clientId}));}checkAlive(){constnow=Date.now();this.clients.forEach((client,clientId)={// 超过60秒没有pong,认为连接已死if(now-client.lastPong60000){console.log(`客户端${clientId}超时,关闭连接`);client.ws.terminate();this.clients.delete(clientId);return;}// 发送pingif(client.ws.readyState===WebSocket.OPEN){client.ws.send(JSON.stringify({type:'ping'}));}});}}客户端心跳实现classWebSocketClient{constructor(url){this.url=url;this.ws=null;this.heartbeatInterval=null;this.reconnectAttempts=0;this.maxReconnectAttempts=5;}connect(){this.ws=newWebSocket(this.url);this.ws.onopen=()={console.log('连接建立');this.reconnectAttempts=0;this.startHeartbeat();};this.ws.onmessage=(event)={constmessage=JSON.parse(event.data);if(message.type==='ping'){// ✅ 收到ping,立即回复pongthis.ws.send(JSON.stringify({type:'pong'}));return;}this.handleMessage(message);};this.ws.onclose=(event)={console.log('连接关闭:',event.code,event.reason);this.stopHeartbeat();this.attemptReconnect();};this.ws.onerror=(error)={console.error('WebSocket错误:',error);};}startHeartbeat(){// 每25秒发送一次pong(服务端30秒检测一次)this.heartbeatInterval=setInterval