[网页五子棋][匹配模块]用户管理器可能存在的问题以及解决办法(线程安全、多开问题)
文章目录
- 线程安全
- 多开
- 原因
- 解决办法
- 相关逻辑更改
线程安全
当前是使用 HashMap
来存储用户的在线状态的,如果是多线程访问同一个 HashMap
,就容易出现线程安全问题
- 每个用户建立连接成功都会调用
afterConnectionEstablished
方法 - 每个用户断开连接都会调用
handleTransportError/afterConnectionClosed
这俩方法
如果同时有多个用户和服务器建立连接/断开连接,此时服务器就是并发的在针对HashMap
进行修改
这里我们就将 game.OnlineUserManager
类中的 HashMap
改为 ConcurrentHashMap
即可
private ConcurrentHashMap<Integer, WebSocketSession> gameHall = new ConcurrentHashMap<>();
多开
原因
当一个用户,同时打开多个浏览器,同时进行登录,进行游戏大厅的时候
- 当浏览器 1 建立
websocket
连接时,服务器这边就会在OnlineUserManager
中保存键值对:userId=1,WebSocketSession=session1
- 当浏览器 2 建立
websocket
连接时,服务器又会在OnlineUserManager
中保存键值对:userId=1,WebSocketSession=session2
- 这两次连接,尝试往哈希表中存储两个键值对,两个键值对的
key
是一样的,后来的value
会覆盖之前的value
出现上述这种覆盖,就会导致第一个浏览器的连接“名存实亡”,已经拿不到对应的WebSocketSession
了,也就无法给这个浏览器推送数据了
解决办法
多开会产生上述问题,但是我们的程序是否应该允许多开呢?
- 对于大部分游戏来说,都是不行的!都是禁止多开的,禁止同一个账号在不同的主机上登录
- 因此我们要做的,不是解决会话覆盖的问题,而是要从源头上禁止游戏多开
- 账号登录成功之后,禁止在其他地方再登录(我们用的方法)
- 账号登录之后,后续其他位置的登录会把前面的登录给踢掉
// 2. 先判定当前的用户是否已经登录过(已经是在线状态),如果是已经在线,就不该继续进行后续逻辑
WebSocketSession tmpSession = onlineUserManager.getFromGameHall(user.getUserId());
if (tmpSession != null) { // 当前用户已经登录过了 // 针对这个情况要告知客户端,你这里重复登录了 MatchResponse response = new MatchResponse(); response.setOk(false); response.setReason("当前禁止多开!"); session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response))); // 一旦调用 close 方法,就会调用下面的 afterConnectionClosed 方法,进行服务下线操作 session.close(); return;
}
相关逻辑更改
在连接建立逻辑这里,做出了判定:如果玩家已经登录过,就不能再登录,同时关闭 websocket
连接
websocket
连接关闭的过程中,会触发afterConnectionClosed
- 在这个方法里,会有一个下线的操作
- 但是在这个下线的方法里,是根据
userId
来进行删除的 - 而这样删除的话,就会把原来的账号也下线,因为多开的时候,每个账号的
userId
都是一样的
所以我们也要在这个 afterConnectionClosed
方法里面也要加入一些判定
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { try { // 玩家下线,从 OnlineUserManager 中删除 User user = (User) session.getAttributes().get("user"); WebSocketSession tmpSession = onlineUserManager.getFromGameHall(user.getUserId()); if (tmpSession == session) { onlineUserManager.exitGameHall(user.getUserId()); } } catch (NullPointerException e) { e.printStackTrace(); MatchResponse response = new MatchResponse(); response.setOk(false); response.setReason("您未登录! 不能进行后续匹配!"); session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response))); }
}
我们在上面的 handleTransportError
方法中,也把相同的位置进行更改