WebSocket房间管理时如何避免用户同时加入同一个房间?

Mc.淑宁 阅读 23

我在用Socket.IO实现多人协作房间时遇到个问题,当两个用户几乎同时点击”加入房间”按钮,服务端会收到两次join事件。虽然客户端都收到成功回调,但服务端日志显示同一个房间ID被多次创建。

我尝试在服务端用if (!rooms.has(roomId))判断后再创建房间,但发现当两个连接几乎同时到达时,第一个连接还没把roomId加入Set,第二个连接就已经开始检查了。比如这样写:

io.on('connection', socket => {
  socket.on('join', ({roomId}) => {
    if (!rooms.has(roomId)) {
      rooms.add(roomId);
      createRoom(roomId); // 这里存在竞态条件
    }
    socket.join(roomId);
  });
});

用setTimeout延时模拟处理耗时后问题复现率降低,但感觉这不是可靠的解决方案。应该用什么方法才能保证同一房间只被创建一次?

我来解答 赞 9 收藏
二维码
手机扫码查看
1 条解答
Code°宏娟
这种竞态条件的问题确实挺烦人的,不过可以用一个简单的锁机制来解决。下面是个靠谱的实现方式:

const roomLocks = new Map();

io.on('connection', socket => {
socket.on('join', async ({roomId}) => {
// 如果房间锁不存在,初始化一个Promise
if (!roomLocks.has(roomId)) {
roomLocks.set(roomId, Promise.resolve());
}

// 获取当前房间的锁
const lock = roomLocks.get(roomId);

// 使用async/await确保串行执行
await lock;
roomLocks.set(roomId, (async () => {
try {
if (!rooms.has(roomId)) {
rooms.add(roomId);
createRoom(roomId); // 只会执行一次
}
socket.join(roomId);
} finally {
// 释放锁
roomLocks.delete(roomId);
}
})());
});
});


核心思路是用Promise链模拟锁机制,保证同一个房间的创建操作按顺序执行。代码里的关键点是roomLocks这个Map,它保存了每个房间对应的Promise。新来的请求必须等待前一个Promise完成才能继续。

复制过去试试,应该能解决你的问题。记得测试下高并发场景,看看是不是稳了。
点赞 6
2026-02-14 20:01