WebSocket房间管理中如何正确处理用户断开连接?

FSD-子聪 阅读 29

我在用 WebSocket 做一个多人实时聊天室,用户加入房间后,如果突然刷新页面或关闭标签页,服务器那边收不到 disconnect 事件,导致房间人数统计一直不准。试过监听 onclose,但有时候根本没触发,咋办?

前端样式这块倒是没问题,比如下面这段 CSS 是用来高亮当前房间的:

.room-item.active {
  border-left: 4px solid #007bff;
  background-color: #f8f9fa;
  font-weight: bold;
}
我来解答 赞 8 收藏
二维码
手机扫码查看
2 条解答
令狐博泽
处理 WebSocket 断开连接的问题,确实有点蛋疼。onclose 有时候不靠谱,得想别的办法。可以试试心跳检测机制,定期发 ping 消息,对方不回 pong 就认为掉线了。代码给你

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

let clients = new Map();

wss.on('connection', function connection(ws) {
const id = Date.now(); // 简单生成个唯一ID
clients.set(id, ws);

ws.on('message', function incoming(message) {
if (message === 'pong') return; // 忽略 pong 消息

// 广播消息给所有客户端
clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});

ws.on('close', () => {
clients.delete(id);
});

// 心跳检测
const interval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send('ping');
} else {
clearInterval(interval);
clients.delete(id);
}
}, 30000); // 每 30 秒 ping 一次
});


记得客户端也要相应地处理 ping 和发送 pong
const socket = new WebSocket('ws://localhost:8080');

socket.onmessage = function(event) {
if (event.data === 'ping') {
socket.send('pong');
return;
}
// 处理其他消息
};


这样基本能解决掉线检测问题。
点赞
2026-03-25 11:04
小盼云
小盼云 Lv1
这个问题其实挺常见的,WebSocket 的 onclose 事件在浏览器直接关闭或刷新时,服务端确实不一定能立刻收到。浏览器关闭太快,关闭帧可能根本没发出去就断了。

最靠谱的方案是心跳检测加服务端超时,别指望 onclose 能百分百可靠。

先说服务端,假设你用的是 Node.js 加 ws 库或者 socket.io,核心思路就是定期发心跳包,超时就踢掉:

const clients = new Map();

// 每30秒检查一次所有连接
setInterval(() => {
clients.forEach((client, ws) => {
if (!client.isAlive) {
ws.terminate();
clients.delete(ws);
updateRoomCount(client.roomId);
return;
}
client.isAlive = false;
ws.ping();
});
}, 30000);

wss.on('connection', (ws) => {
const client = { isAlive: true, roomId: null };
clients.set(ws, client);

ws.on('pong', () => {
client.isAlive = true;
});

ws.on('close', () => {
clients.delete(ws);
updateRoomCount(client.roomId);
});
});


前端这边配合一下,收到 ping 自动回复 pong,另外可以在 beforeunload 时主动断开:

let ws = null;

function connect() {
ws = new WebSocket('ws://your-server');

ws.onmessage = (e) => {
if (e.data === 'ping') {
ws.send('pong');
}
};
}

window.addEventListener('beforeunload', () => {
if (ws) {
ws.close(1000, 'page unload');
}
});


不过 beforeunload 也不是百分百可靠,比如浏览器崩溃、断电这些极端情况。所以核心还是服务端的心跳超时机制,这才是兜底方案。

还有一点,房间人数统计建议用 Redis 或者内存里的 Map 单独维护,别每次都去遍历连接数,这样更清晰也更高效。

你提到那段 CSS 是用来高亮房间的,样式本身没问题,但如果你在清理用户连接时没正确更新 DOM,高亮状态可能也会残留。记得在清理用户连接时,同步更新前端的房间列表状态。
点赞 2
2026-03-02 19:37