文章目录
- 处理连接成功
- 通知玩家就绪
- 逻辑图
- 问题 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
,所以在先手选择都是选择对方
解决办法: