用户匹配对手时遵循这么一个原则:用户 A 找到用户 B,由用户 A 负责一切工作,既由用户 A 完成创建匹配数据并保存到缓存的全部操作。值得注意的一点是,在匹配时要注意保证状态的变化:
当前用户在匹配对手的同时,被其他用户匹配,那么当前用户应当停止匹配操作
当前用户匹配到对手,但对手被其他用户匹配了,那么当前用户应该重新寻找新的对手
用户匹配对手的过程应该保证原子性,使用 Java 锁来保证
/** * 用户随机匹配对手 */ @SneakyThrows private void matchUser(JSONObject jsonObject) { log.info("ChatWebsocket matchUser 用户随机匹配对手开始 message: {}, userId: {}", jsonObject.toJSONString(), userId); MessageReply<GameMatchInfo> messageReply = new MessageReply<>(); ChatMessage<GameMatchInfo> result = new ChatMessage<>(); result.setSender(userId); result.setType(MessageTypeEnum.MATCH_USER); lock.lock(); try { // 设置用户状态为匹配中 matchCacheUtil.setUserInMatch(userId); matchCond.signal(); } finally { lock.unlock(); } // 创建一个异步线程任务,负责匹配其他同样处于匹配状态的其他用户 Thread matchThread = new Thread(() -> { boolean flag = true; String receiver = null; while (flag) { // 获取除自己以外的其他待匹配用户 lock.lock(); try { // 当前用户不处于待匹配状态 if (matchCacheUtil.getUserOnlineStatus(userId).compareTo(StatusEnum.IN_GAME) == 0 || matchCacheUtil.getUserOnlineStatus(userId).compareTo(StatusEnum.GAME_OVER) == 0) { log.info("ChatWebsocket matchUser 当前用户 {} 已退出匹配", userId); return; } // 当前用户取消匹配状态 if (matchCacheUtil.getUserOnlineStatus(userId).compareTo(StatusEnum.IDLE) == 0) { // 当前用户取消匹配 messageReply.setCode(MessageCode.CANCEL_MATCH_ERROR.getCode()); messageReply.setDesc(MessageCode.CANCEL_MATCH_ERROR.getDesc()); Set<String> set = new HashSet<>(); set.add(userId); result.setReceivers(set); result.setType(MessageTypeEnum.CANCEL_MATCH); messageReply.setChatMessage(result); log.info("ChatWebsocket matchUser 当前用户 {} 已退出匹配", userId); sendMessageAll(messageReply); return; } receiver = matchCacheUtil.getUserInMatchRandom(userId); if (receiver != null) { // 对手不处于待匹配状态 if (matchCacheUtil.getUserOnlineStatus(receiver).compareTo(StatusEnum.IN_MATCH) != 0) { log.info("ChatWebsocket matchUser 当前用户 {}, 匹配对手 {} 已退出匹配状态", userId, receiver); } else { matchCacheUtil.setUserInGame(userId); matchCacheUtil.setUserInGame(receiver); matchCacheUtil.setUserInRoom(userId, receiver); flag = false; } } else { // 如果当前没有待匹配用户,进入等待队列 try { log.info("ChatWebsocket matchUser 当前用户 {} 无对手可匹配", userId); matchCond.await(); } catch (InterruptedException e) { log.error("ChatWebsocket matchUser 匹配线程 {} 发生异常: {}", Thread.currentThread().getName(), e.getMessage()); } } } finally { lock.unlock(); } } UserMatchInfo senderInfo = new UserMatchInfo(); UserMatchInfo receiverInfo = new UserMatchInfo(); senderInfo.setUserId(userId); senderInfo.setScore(0); receiverInfo.setUserId(receiver); receiverInfo.setScore(0); matchCacheUtil.setUserMatchInfo(userId, JSON.toJSONString(senderInfo)); matchCacheUtil.setUserMatchInfo(receiver, JSON.toJSONString(receiverInfo)); GameMatchInfo gameMatchInfo = new GameMatchInfo(); List<Question> questions = questionSev.getAllQuestion(); gameMatchInfo.setQuestions(questions); gameMatchInfo.setSelfInfo(senderInfo); gameMatchInfo.setOpponentInfo(receiverInfo); messageReply.setCode(MessageCode.SUCCESS.getCode()); messageReply.setDesc(MessageCode.SUCCESS.getDesc()); result.setData(gameMatchInfo); Set<String> set = new HashSet<>(); set.add(userId); result.setReceivers(set); result.setType(MessageTypeEnum.MATCH_USER); messageReply.setChatMessage(result); sendMessageAll(messageReply); gameMatchInfo.setSelfInfo(receiverInfo); gameMatchInfo.setOpponentInfo(senderInfo); result.setData(gameMatchInfo); set.clear(); set.add(receiver); result.setReceivers(set); messageReply.setChatMessage(result); sendMessageAll(messageReply); log.info("ChatWebsocket matchUser 用户随机匹配对手结束 messageReply: {}", JSON.toJSONString(messageReply)); }, CommonField.MATCH_TASK_NAME_PREFIX + userId); matchThread.start(); }项目展示
项目代码如下:https://github.com/Yee-Q/match-project
跑起来后,使用 websocket-client 可以进行测试。在浏览器打开,在控制台查看消息。
在连接输入框随便输入一个数字作为 userId,点击连接,此时客户端就和服务端建立 WebSocket 连接了