当前位置: 首页 > news >正文

[网页五子棋][对战模块]处理连接成功,通知玩家就绪,逻辑问题(线程安全,先手判定错误)

文章目录

  • 处理连接成功
  • 通知玩家就绪
  • 逻辑图
  • 问题 1:线程安全
  • 问题 2:先手判定错误
    • 两边都是提示:轮到对方落子![image.png](https://i-blog.csdnimg.cn/img_convert/c570cd26eadbe87ed467bc4edaa7945e.png)

处理连接成功

实现 GameAPIafterConnectionEstablished 方法

  • 首先需要检测用户的登录状态(从 session 中拿到当前用户信息)
  • 然后要判定当前玩家是否是在房间中
  • 接下来进行多开判定,如果玩家已经在游戏中,则不能再次连接
  • 把两个玩家放到对应的房间对象中,当两个玩家都建立了连接,房间就放满了,这个时候通知双方都准备就绪
  • 如果有第三个玩家尝试也想加入房间,则给出一个提示,房间已经满了
@Override  
public void afterConnectionEstablished(WebSocketSession session) throws Exception {  GameReadyResponse resp = new GameReadyResponse();  // 1. 先获取到用户的身份信息(从 HttpSession 里面拿到当前用户的对象)  User user = (User) session.getAttributes().get("user");  if(user == null) {  resp.setOk(false);  resp.setReason("用户尚未登录!");  session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));  return;  }  // 2. 判定当前用户是否已经在房间里(拿房间管理器 RoomManager 进行查询)  Room room = roomManager.getRoomByUserId(user.getUserId());  if (room == null) {  // 如果为 null,当前没有找到对应的房间,就是该玩家还没有匹配到  resp.setOk(false);  resp.setReason("用户尚未匹配到!");  session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));  return;  }  // 3. 判定当前是不是多开(该用户是不是已经在其他地方进入游戏了)  //    前面准备了一个 OnlineUserManager    if (onlineUserManager.getFromGameRoom(user.getUserId()) != null  || onlineUserManager.getFromGameHall(user.getUserId()) != null) {  // 如果一个账号,一边是在游戏大厅,一边是在游戏房间,也视为多开  resp.setOk(false);  resp.setReason("禁止多开游戏页面!");  session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));  }  // 4. 设置当前玩家上线  onlineUserManager.enterGameHall(user.getUserId(), session);  // 5. 把两个玩家加入到游戏房间中  //    当前这个逻辑是在 game_room.html 页面加载的时候进行的,  //    前面的创建房间/匹配过程,是在 game_hall.html 页面中完成的  //    因此前面匹配到对手之后,需要经过页面跳转,来到 game_room.html 才算正式进入游戏房间(玩家准备就绪)  //    直行到当前逻辑,说明玩家已经页面跳转成功了  //    页面跳转是很有可能出现失败的情况的(成本高,风险大)  if (room.getUser1() == null) {  // 第一个玩家还未加入房间,就把当前连上 websocket 的玩家作为 user1 加入到房间中  room.setUser1(user);  // 把先连入房间的玩家作为先手  room.setWhiteUser(user.getUserId());  System.out.println("玩家 " + user.getUsername() + " 作为玩家1,已经准备就绪!");  return;}  if (room.getUser2() == null) {  // 如果进入这个逻辑,说明玩家1 已经加入房间,现在要给当前玩家作为玩家2  room.setUser2(user);  System.out.println("玩家 " + user.getUsername() + " 作为玩家2,已经准备就绪!");  // 当两个玩家都加入成功之后,就要让服务器,给这两个玩家都返回 websocket 的响应数据  // 通知这两个玩家:游戏双方都已经准备好了  // 通知玩家1  noticeGameReady(room, room.getUser1(), room.getUser2());  // 通知玩家2  noticeGameReady(room, room.getUser2(), room.getUser1());  return;  }  // 6. 此处又有玩家尝试连接同一个房间,就提示报错  //    这种情况理论上是不存在的,为了让程序更加的健壮,还是做一个判定和提示  resp.setOk(false);  resp.setReason("当前房间已满,你不能加入房间!");  session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
}  private void noticeGameReady(Room room, User thisUser, User thatUser) throws IOException {  GameReadyResponse resp = new GameReadyResponse();  resp.setMessage("gameReady");  resp.setOk(true);  resp.setReason("");  resp.setRoomId(room.getRoomId());  resp.setThisUserId(thisUser.getUserId());  resp.setThatUserId(thatUser.getUserId());  resp.setWhiteUser(room.getWhiteUser());  // 把当前的响应数据传回给玩家  WebSocketSession session = onlineUserManager.getFromGameRoom(thisUser.getUserId());  session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
}

image.png

  • 之前已经写了一个 OnlineUserManager 对象了,也确实能够管理一些用户的在线状态
  • 但是这个状态仅仅是局限在 game_hall 这个页面中
  • 现在我们已经是在 game_room 中了

image.png

  • 之前在退出 game_hall 页面的时候,就会断开 websocket 连接,也就会在服务器的 OnlineUserManager 中删掉对应的元素

因此玩家从游戏大厅离开之后,进入游戏房间页面的时候,就需要重新管理用户的在线状态了

通知玩家就绪

@Override  
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {  User user = (User) session.getAttributes().get("user");  if (user == null) {  // 此处我们简单处理,在断开连接的时候就不给客户端返回响应了  return;  }  WebSocketSession existSession = onlineUserManager.getFromGameRoom(user.getUserId());  if(session == existSession) {  // 加上这个判定,目的是为了避免在多开的情况下,第二个用户退出连接动作,导致第一个用户的会话被删除  onlineUserManager.exitGameRoom(user.getUserId());  }  System.out.println("当前用户 " + user.getUsername() + " 游戏房间连接异常!");  
}

逻辑图

对战模块-连接房间.png

问题 1:线程安全

在玩家 1 连入服务器和玩家 2 连入服务器这两个操作,就是并发的

  • 不能保证是上面逻辑图中 1 先建立,2 后建立的顺序
  • 这就意味着代码中的这段逻辑是存在多线程环境下调用的,可能会出现线程安全问题 image.png
  • 我们就要把这里的逻辑判定,使用锁保护起来,避免多个客户端都认为自己是先手方

需要竞争的资源是什么,就对什么加锁

  • 对谁加锁,针对这个对象访问的时候才有互斥效果
  • 此时我们是多个线程在同时访问、修改同一个 room 对象
  • 所以我们需要针对 room 对象加锁

image.png|529

  • 要保证玩家 1 和 2 要互斥,玩家 3 和 4 要互斥,玩家 5 和 6 要互斥
  • 同个房间里的两个对象才会有竞争,非同房里面的玩家互不干扰image.png|471

问题 2:先手判定错误

两边都是提示:轮到对方落子image.png

image.png|366

  • 客户端代码中尝试获取响应中的 isWhite 字段
  • 但是实际响应的数据中,根本就灭有 isWhite 字段,有的是 whiteUser 字段image.png
  • 因此代码中进行取这个字段,就都取到了一个 undefined,这里的判断结果都为 false,所以在先手选择都是选择对方image.png

解决办法:image.png
image.png

相关文章:

  • [Windows]在Win上安装bash和zsh - 一个脚本搞定
  • openssl 怎么生成吊销列表
  • Docker容器创建Redis主从集群
  • 如何排查Redis单个Key命中率骤降?
  • 【Linux系统编程】Ext系列文件系统
  • Ansible 剧本精粹 - 编写你的第一个 Playbook
  • 告别手动绘图!基于AI的Smart Mermaid自动可视化图表工具搭建与使用指南
  • Vue拖拽组件:vue-draggable-plus
  • 如何设计一个支持线上线下的通用订单模块 —— 面向本地生活服务行业的架构思路
  • Portainer安装指南:多节点监控的docker管理面板-家庭云计算专家
  • docker 部署 gin
  • 模型训练相关的问题
  • CFTel:一种基于云雾自动化的鲁棒且可扩展的远程机器人架构
  • 实现RabbitMQ多节点集群搭建
  • 初学者如何微调大模型?从0到1详解
  • 基于Python与本地Ollama的智能语音唤醒助手实现
  • RV1126-OPENCV 图像叠加
  • Rust 学习笔记:发布一个 crate 到 crates.io
  • 性能优化 - 工具篇:基准测试 JMH
  • 性能优化 - 案例篇:数据一致性
  • 河北建设委员会网站首页/商丘网站建设公司
  • 南昌seo网站推广费用/搜索引擎营销的名词解释
  • 国外优秀设计网站有哪些/惠州网站建设
  • 管理一个网站的后台怎么做/企业网站推广有哪些方式
  • 想在网站里添加超链接怎么做/软文营销实施背景
  • 湖州做网站的/代运营服务