最简websocket服务可用ws库直接启动:npm install ws后创建server.js并运行node server.js,即可通过new websocket('ws://localhost:8080')快速验证连通性;生产环境需检查ws.readystate === websocket.open再发送消息,并用map管理clients且监听close/error及时清理。

直接用 ws 启动最简服务,别绕弯
想快速验证 WebSocket 是否通,ws 库本身就能开一个裸服务,不需要 Express、HTTP 服务器或任何中间层。很多新手卡在“必须先搭个 Web 服务才能上 WebSocket”,其实完全没必要。
执行以下命令即可监听 8080 端口:
npm install ws
新建 server.js:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
console.log('新连接已建立');
ws.send('欢迎连接到 WebSocket 服务');
ws.on('message', (data) => {
console.log('收到:', data.toString());
ws.send(`回声: ${data.toString()}`);
});
ws.on('close', () => console.log('连接已关闭'));
});
运行 node server.js,再用浏览器控制台执行 new WebSocket('ws://localhost:8080') 就能连上。这是验证协议通路的最快方式,也是排查“连不上”问题的第一步。
wss.on('connection') 回调里必须做连接状态检查
真实环境中,客户端可能断网重连、页面刷新、代理中断,ws 对象的状态不会自动同步。如果直接对 ws.send() 调用而不检查,会抛出 WebSocket is not open 错误,甚至导致进程崩溃(默认未捕获)。
正确做法是每次发消息前加判断:
if (ws.readyState === WebSocket.OPEN) { ws.send(...); }- 不要依赖
ws.on('open')—— 它只在握手成功时触发一次,之后状态变化(如网络抖动)不会再次触发 - 若需主动探测连接健康度,可定期发
ping帧:ws.ping(),并监听ws.on('pong')
漏掉这个检查,上线后遇到弱网用户就会频繁报错,但本地开发几乎复现不了。
用 http.createServer 套一层,是为了共用端口和静态资源
当你要提供 HTML 页面(比如 index.html)和 WebSocket 服务时,不能让前端开两个端口(一个 HTTP,一个 WS)。否则会遇到跨域或部署麻烦。
这时必须把 ws 挂在 http 服务器上,靠 upgrade 事件接管 WebSocket 握手:
const http = require('http');
const WebSocket = require('ws');
const express = require('express');
const app = express();
app.use(express.static('public')); // 提供 /index.html
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws, req) => {
// req 可读取 query 参数,比如 ?room=general
const room = new URL(req.url, 'http://f').searchParams.get('room');
console.log('加入房间:', room);
});
关键点:
-
server.listen(3000)启动后,HTTP 和 WS 共享3000端口 - 前端访问
http://localhost:3000/加载页面,再用new WebSocket('ws://localhost:3000/')连接,零跨域 - 千万别写成
new WebSocket('ws://localhost:3000/ws')并试图在 Express 里配路由 ——ws不走 Express 中间件链
clients 存储结构选 Map 而不是数组,且要清理 close 事件
广播消息、私聊、踢人等逻辑都依赖客户端列表。用 Array 存 ws 实例看似简单,但有两个硬伤:
- 无法通过 ID 快速查找:每次找用户都要
find遍历,O(n);而Map支持get(clientId),O(1) - 不清理
close后的残留引用:ws关闭后对象仍留在数组里,下次广播会调用ws.send()报错
标准写法是:
const clients = new Map(); // key 是 clientId,value 是 ws 实例
wss.on('connection', (ws) => {
const id = Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
clients.set(id, ws);
ws.on('close', () => clients.delete(id)); // 必须在这里删
ws.on('error', () => clients.delete(id)); // error 也可能导致不可用,一并删
});
漏掉 delete,跑几天后内存持续上涨,clients.size 越来越大,但实际在线人数为 0 —— 这是线上最隐蔽的内存泄漏来源之一。











