用 ws 库5分钟搭好Node.js聊天室:服务端监听connection分配ID、监听message需toString()、监听close/error及时清理;前端需手动处理onopen/onerror并检查readyState再send;广播前必须校验client.readyState === WebSocket.OPEN。

直接用 ws 库搭一个可运行的 Node.js 聊天室,5 分钟内能跑起来 —— 前提是你已装好 Node.js。不需要框架、不配路由、不写打包配置,纯靠原生 WebSocket API 和极简服务端逻辑。
为什么选 ws 而不是 socket.io
ws 是最轻量、最贴近协议本意的实现:它不做自动重连、不加私有协议层、不隐藏连接细节。这意味着:
- 出问题时你能立刻看到是
connection没触发,还是message事件没监听上 - 不会因为
socket.io的connect/reconnect/disconnect多层抽象而误判“用户已在线” -
ws的send()方法只接受string或Buffer,强制你处理msg.toString()—— 这恰恰是新手最容易漏掉的坑
wss.on("connection") 里必须做三件事
每次有新客户端连上来,ws 会触发一次 connection 事件。这里不是“存个连接对象”就完事了,得立刻建立基础通信契约:
- 给这个连接分配唯一标识(比如用
Date.now() + Math.random()生成id),否则广播时无法区分谁是谁 - 监听
ws.on("message"),且必须对msg调用toString()—— 否则收到的是Buffer,JSON.parse()直接报SyntaxError: Unexpected token in JSON at position 0 - 监听
ws.on("close")和ws.on("error"),并从你的连接数组中移除该实例;否则断开的连接仍留在内存里,广播时会触发WebSocket is not open: readyState 0错误
前端 WebSocket 实例必须手动处理 onopen 和 onerror
浏览器原生 WebSocket 不会自动重试,也不合并状态。常见错误现象:
- 页面刚打开就发消息 → 报错
Failed to execute 'send' on 'WebSocket': Still in CONNECTING state - 网络抖动后连接断开 → 页面无任何提示,用户继续输入,消息石沉大海
所以必须显式写:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
const socket = new WebSocket("ws://127.0.0.1:3000");
socket.onopen = () => console.log("✅ 已连接");
socket.onerror = (e) => console.error("❌ 连接异常", e);
socket.onclose = () => console.log("⚠️ 连接已关闭,请刷新重试");
// 发送前务必检查
function sendMessage(data) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(data));
}
}
广播逻辑别硬写 for 循环,先过滤无效连接
wss.clients 是个 Set,但它不保证所有成员都处于 OPEN 状态 —— 断开中的连接、握手失败的连接、被中间代理切断的连接,都可能还留在里面。直接遍历并 send() 会抛错。
正确做法是加一层状态校验:
wss.on("connection", (ws) => {
ws.on("message", (msg) => {
const data = JSON.parse(msg.toString());
// 广播前过滤
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
from: "system",
content: `${data.username}: ${data.text}`
}));
}
});
});
});
更稳妥的写法是用 Array.from(wss.clients) 转成数组再 filter,但对小规模聊天室,上面的 if 判断已足够。
真正容易被忽略的是:服务端没做连接数限制,本地测试开 10 个标签页没问题,但部署到服务器后,一个恶意脚本反复建连就能把 wss.clients 塞爆 —— 这点在 Demo 阶段可以不管,上线前必须加 maxConnections 或 IP 限频。

















