Flask-SocketIO等待队列匹配玩家时触发KeyError线程错误求助
全局匹配队列中join_room抛出KeyError的解决方案
问题背景
两名客户端可选择在线游玩:
具体流程为加入全局等待队列:
- 客户端每秒向服务器发送
join_random()请求,查询是否有可加入的房间,前端代码:
function joinRandomGame() { socket.emit('join_random', { game : window.roomVariables.game, user_id : window.roomVariables.user_id }); setInterval(joinRandomGame, 1000); }
- 服务器端
join_random事件处理逻辑:检查全局等待队列,若队列中玩家数≥2,则创建房间并通过socket通知客户端进入游戏,后端代码:
from threading import Lock from collections import deque userQueueLock = Lock() userQueue = deque() @socketio.on('join_random') def handle_join_random(data): game = data['game'] user_id = data['user_id'] print(userQueue) with userQueueLock: if user_id not in userQueue: userQueue.append(user_id) if len(userQueue) >= 2: player1 = userQueue.popleft() player2 = userQueue.popleft() room_id = generate_random_key() room = Room( id=room_id, users=[player1, player2], points=[0, 0], ... ) db.session.add(room) db.session.commit() # 此处触发线程错误 join_room(room_id, sid=player1) join_room(room_id, sid=player2) emit('play_game',{'game': game, 'room_id': room_id}, room=room_id)
错误现象
当两名客户端都进入队列后,调用join_room时抛出KeyError,错误日志如下:
deque(['user_1709895093301', 'user_1707675752654']) Exception in thread Thread-104 (_handle_event_internal): Traceback (most recent call last): File "/usr/lib/python3.10/threading.py", line 1016, in _bootstrap_inner self.run() File "/usr/lib/python3.10/threading.py", line 953, in run self._target(*self._args, **self._kwargs) File "/home/taylor/Desktop/Projects/web_dev/Projects/Flask/Tic-Tac-Toe-Web/venv/lib/python3.10/site-packages/socketio/server.py", line 586, in _handle_event_internal r = server._trigger_event(data[0], namespace, sid, *data[1:]) File "/home/taylor/Desktop/Projects/web_dev/Projects/Flask/Tic-Tac-Toe-Web/venv/lib/python3.10/site-packages/socketio/server.py", line 611, in _trigger_event return handler(*args) File "/home/taylor/Desktop/Projects/web_dev/Projects/Flask/Tic-Tac-Toe-Web/venv/lib/python3.10/site-packages/flask_socketio/__init__.py", line 282, in _handler return self._handle_event(handler, message, namespace, sid, File "/home/taylor/Desktop/Projects/web_dev/Projects/Flask/Tic-Tac-Toe-Web/venv/lib/python3.10/site-packages/flask_socketio/__init__.py", line 827, in _handle_event ret = handler(*args) File "/home/taylor/Desktop/Projects/web_dev/Projects/Flask/Tic-Tac-Toe-Web/socketio_events.py", line 125, in handle_join_random join_room(room_id, sid=player1) File "/home/taylor/Desktop/Projects/web_dev/Projects/Flask/Tic-Tac-Toe-Web/venv/lib/python3.10/site-packages/flask_socketio/__init__.py", line 1035, in join_room socketio.server.enter_room(sid, room, namespace=namespace) File "/home/taylor/Desktop/Projects/web_dev/Projects/Flask/Tic-Tac-Toe-Web/venv/lib/python3.10/site-packages/socketio/server.py", line 282, in enter_room self.manager.enter_room(sid, namespace, room) File "/home/taylor/Desktop/Projects/web_dev/Projects/Flask/Tic-Tac-Toe-Web/venv/lib/python3.10/site-packages/socketio/manager.py", line 71, in enter_room return self.basic_enter_room(sid, namespace, room, eio_sid=eio_sid) File "/home/taylor/Desktop/Projects/web_dev/Projects/Flask/Tic-Tac-Toe-Web/venv/lib/python3.10/site-packages/socketio/base_manager.py", line 112, in basic_enter_room eio_sid = self.rooms[namespace][None][sid] File "/home/taylor/Desktop/Projects/web_dev/Projects/Flask/Tic-Tac-Toe-Web/venv/lib/python3.10/site-packages/bidict/_base.py", line 523, in __getitem__ return self._fwdm[key] KeyError: 'user_1709895093301'
问题原因
- 混淆user_id与socket sid:代码中把自定义业务层的
user_id当作SocketIO的连接会话ID(sid)传入join_room,但join_room需要的是SocketIO内部用于追踪活跃连接的sid,找不到对应sid就会抛出KeyError。 - 重复请求导致队列异常:客户端每秒发送一次请求,可能导致同一用户多次加入队列,后续处理时对应的连接可能已断开或sid关联失效。
解决方案
方案1:正确传递socket sid
前端发送请求时传递SocketIO内置的sid(通过socket.id获取),后端队列存储sid而非user_id:
前端修改:
let joinInterval; function joinRandomGame() { socket.emit('join_random', { game: window.roomVariables.game, sid: socket.id // 传递SocketIO会话ID }); } // 启动定时请求 joinInterval = setInterval(joinRandomGame, 1000);
后端修改:
from threading import Lock from collections import deque userQueueLock = Lock() userQueue = deque() @socketio.on('join_random') def handle_join_random(data): game = data['game'] current_sid = data['sid'] print(userQueue) with userQueueLock: # 避免同一连接重复加入队列 if current_sid not in userQueue: userQueue.append(current_sid) if len(userQueue) >= 2: player1_sid = userQueue.popleft() player2_sid = userQueue.popleft() room_id = generate_random_key() room = Room( id=room_id, # 若需存储用户ID,需从sid关联的会话中获取 users=[get_user_id_by_sid(player1_sid), get_user_id_by_sid(player2_sid)], points=[0, 0], ... ) db.session.add(room) db.session.commit() # 使用正确的sid调用join_room join_room(room_id, sid=player1_sid) join_room(room_id, sid=player2_sid) emit('play_game',{'game': game, 'room_id': room_id}, room=room_id) # 辅助函数:通过sid获取用户ID(需结合自身用户会话实现) def get_user_id_by_sid(sid): from flask import session return session.get('user_id', None)
方案2:清理断开连接的用户
监听socket_disconnect事件,将断开的连接从队列中移除,避免无效sid残留:
@socketio.on('disconnect') def handle_disconnect(): with userQueueLock: if request.sid in userQueue: userQueue.remove(request.sid)
方案3:停止重复请求
客户端收到匹配成功的play_game事件后,立即停止发送join_random请求:
// 在前端添加监听逻辑 socket.on('play_game', () => { clearInterval(joinInterval); });
内容的提问来源于stack exchange,提问作者Taylor Threader
相关产品推荐
相关产品推荐

