[网页五子棋][对战模块]处理连接成功,通知玩家就绪,逻辑问题(线程安全,先手判定错误)
文章目录
- 处理连接成功
- 通知玩家就绪
- 逻辑图
- 问题 1:线程安全
- 问题 2:先手判定错误
- 两边都是提示:轮到对方落子
处理连接成功
实现 GameAPI
的 afterConnectionEstablished
方法
- 首先需要检测用户的登录状态(从
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)));
}
- 之前已经写了一个
OnlineUserManager
对象了,也确实能够管理一些用户的在线状态 - 但是这个状态仅仅是局限在
game_hall
这个页面中 - 现在我们已经是在
game_room
中了
- 之前在退出
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() + " 游戏房间连接异常!");
}
逻辑图
问题 1:线程安全
在玩家 1 连入服务器和玩家 2 连入服务器这两个操作,就是并发的
- 不能保证是上面逻辑图中 1 先建立,2 后建立的顺序
- 这就意味着代码中的这段逻辑是存在多线程环境下调用的,可能会出现线程安全问题
- 我们就要把这里的逻辑判定,使用锁保护起来,避免多个客户端都认为自己是先手方
需要竞争的资源是什么,就对什么加锁
- 对谁加锁,针对这个对象访问的时候才有互斥效果
- 此时我们是多个线程在同时访问、修改同一个
room
对象 - 所以我们需要针对
room
对象加锁
- 要保证玩家 1 和 2 要互斥,玩家 3 和 4 要互斥,玩家 5 和 6 要互斥
- 同个房间里的两个对象才会有竞争,非同房里面的玩家互不干扰
问题 2:先手判定错误
两边都是提示:轮到对方落子
- 客户端代码中尝试获取响应中的
isWhite
字段 - 但是实际响应的数据中,根本就灭有
isWhite
字段,有的是whiteUser
字段 - 因此代码中进行取这个字段,就都取到了一个
undefined
,这里的判断结果都为false
,所以在先手选择都是选择对方
解决办法: