WebSocket 在 JavaScript 中的高级应用:从原型到生产环境
\nWebSocket 是一种协议,通过一个持久的 TCP 连接在客户端和服务器之间提供全双工通信。在之前的文章中,我们讨论了基础知识:建立连接、发送消息和简单的聊天。然而,要构建真正可靠且可扩展的实时应用(在线游戏、交易平台、协作编辑系统),必须掌握高级技术。在本文中,我们将探讨 Node.js 上生产级 WebSocket 服务器的架构,包括扩展、状态管理、授权和错误处理。
\n\n1. 高级服务器架构与房间管理
\n最简单的 WebSocket 服务器(基于 ws 库)在一个全局对象中处理所有客户端。在实际项目中,需要逻辑隔离——房间和频道。
考虑一个使用 RoomManager 类的架构示例:
const WebSocket = require('ws');\n\nclass RoomManager {\n constructor() {\n this.rooms = new Map(); // roomName -> Set<WebSocket>\n }\n\n join(ws, roomName) {\n if (!this.rooms.has(roomName)) {\n this.rooms.set(roomName, new Set());\n }\n this.rooms.get(roomName).add(ws);\n ws.room = roomName; // 在套接字上保存房间\n }\n\n leave(ws) {\n if (ws.room && this.rooms.has(ws.room)) {\n this.rooms.get(ws.room).delete(ws);\n if (this.rooms.get(ws.room).size === 0) {\n this.rooms.delete(ws.room);\n }\n }\n }\n\n broadcast(roomName, message, excludeWs = null) {\n if (!this.rooms.has(roomName)) return;\n this.rooms.get(roomName).forEach(client => {\n if (client !== excludeWs && client.readyState === WebSocket.OPEN) {\n client.send(JSON.stringify(message));\n }\n });\n }\n}\n\n// 使用\nconst wss = new WebSocket.Server({ port: 8080 });\nconst roomManager = new RoomManager();\n\nwss.on('connection', (ws) => {\n ws.on('message', (data) => {\n const msg = JSON.parse(data);\n switch (msg.type) {\n case 'join':\n roomManager.join(ws, msg.room);\n break;\n case 'message':\n roomManager.broadcast(ws.room, { user: msg.user, text: msg.text }, ws);\n break;\n }\n });\n\n ws.on('close', () => roomManager.leave(ws));\n});\n这段代码允许创建隔离的聊天或游戏会话。请注意关闭连接的处理——这对于防止内存泄漏至关重要。
\n\n2. 扩展:集群与 Redis Pub/Sub
\n单个 Node.js 进程无法有效利用超过一个 CPU 核心。为了扩展,我们使用内置的 cluster 模块。然而,问题出现了:WebSocket 连接绑定到特定的工作进程。如何向连接到另一个工作进程的用户发送消息?
解决方案是外部消息代理,例如 Redis Pub/Sub。
\nconst cluster = require('cluster');\nconst http = require('http');\nconst { Server } = require('ws');\nconst redis = require('redis');\n\nconst REDIS_CHANNEL = 'ws:messages';\n\nif (cluster.isMaster) {\n // 根据核心数启动工作进程\n const numCPUs = require('os').cpus().length;\n for (let i = 0; i < numCPUs; i++) {\n cluster.fork();\n }\n\n cluster.on('exit', (worker) => {\n console.log(`Worker ${worker.process.pid} died`);\n cluster.fork(); // 重启\n });\n} else {\n // 工作进程\n const server = http.createServer();\n const wss = new Server({ server });\n\n // 连接到 Redis\n const pub = redis.createClient();\n const sub