当前位置: 首页 > 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

http://www.xdnf.cn/news/758269.html

相关文章:

  • Spring Boot,注解,@ComponentScan
  • linux驱动开发(1)-内核模块
  • rl_sar功能包详解
  • pyqt5笔记20250601
  • gitflow
  • 《Pytorch深度学习实践》ch2-梯度下降算法
  • 设计模式——状态设计模式(行为型)
  • 设计模式——代理设计模式(结构型)
  • android stdio 的布局属性
  • 鸿蒙ArkTS | Badge 信息标记组件自学指南
  • MyBatis03——SpringBoot整合MyBatis
  • Kubernetes(K8s)核心架构解析与实用命令大全
  • Go 语言 select 语句详解
  • JMeter 性能测试
  • DDR5 ECC详细原理介绍与基于协议讲解
  • 3D Gaussian splatting 05: 代码阅读-训练整体流程
  • 【计算机网络】第3章:传输层—面向连接的传输:TCP
  • Spring Boot中Excel处理完全指南:从基础到高级实践
  • telnet 基本用法
  • Java并发编程中任务调度与线程池的配置优化
  • 大规模真实场景 WiFi 感知基准数据集
  • SSL/TLS 协议详解:安全通信的基石
  • C++修炼:位图和布隆过滤器
  • 布隆筛选详解
  • Ansible自动化运维工具全面指南:从安装到实战应用
  • 【Go语言生态】
  • Vue初始化脚手架
  • 数据库,Spring Boot,数据源
  • 第13讲、Odoo 18 配置文件(odoo.conf)详细解读
  • 6.1 英语复习笔记 3