文章目錄
- config.WebSocketConfig
- 將鍵值對加?OnlineUserManager中
- 線程安全、鎖
- ObjectMapper來處理json
- 針對多開情況的判定
- 處理連接關閉、異常(玩家中途退出)后的不合理操作
- 游戲大廳數據更新
config.WebSocketConfig
把MatchAPI注冊進去
? 在addHandler 之后,再加上?個.addInterceptors(newHttpSessionHandshakeInterceptor()) 代碼,這樣可以把之前登錄過程中往HttpSession中存放的數據(主要是User對象),到WebSocket的session中.?便后?的代碼中獲取到當前用戶信息
// 通過 .addInterceptors(new HttpSessionHandshakeInterceptor() 這個操作來把 HttpSession ?的屬性放到 WebSocket 的 session 中 // 參考: https://docs.spring.io/springframework/docs/5.0.7.RELEASE/spring-framework-reference/web.html#websocketserver-handshake// 然后就可以在 WebSocket 代碼中 WebSocketSession ?拿到 HttpSession 中的 attribute.registry.addHandler(matchAPI, "/findMatch").addInterceptors(new HttpSessionHandshakeInterceptor());}
將鍵值對加?OnlineUserManager中
? 當玩家斷開websocket連接,則將鍵值對從OnlineUserManager中刪除
? 在玩家連接好的過程中,隨時可以通過userId來查詢到對應的會話,以便向客戶端返回數據
由于存在兩個??,游戲?廳和游戲房間,使?兩個哈希表來分別存儲兩部分的會話
線程安全、鎖
由于handlerMatch 在單獨的線程中調?.因此要考慮到訪問隊列的線程安全問題.需要加上鎖
? 每個隊列分別使?隊列對象本?作為鎖即可.
? 在??處使?wait來等待,直到隊列中達到2個元素及其以上,才喚醒線程消費隊列
private void handlerMatch(Queue<User> matchQueue) {synchronized (matchQueue){try {// 很可能隊列初始情況為0,用while循環檢查,不能用if(匹配成功需要有兩個玩家)while (matchQueue.size() < 2){matchQueue.wait();return;}//從隊列中取出兩個玩家User player1 = matchQueue.poll();User player2 = matchQueue.poll();System.out.println("匹配出了兩個玩家:"+player1.getUsername()+" , "+player2.getUsername());//獲取玩家的websocket的會話,告訴玩家 排到了WebSocketSession session1 = onlineUserManager.getFromGameHall(player1.getUserId());WebSocketSession session2 = onlineUserManager.getFromGameHall(player2.getUserId());//理論上來說,匹配隊列中的玩家一定是在線狀態(前面已經處理過,斷開連接的玩家會被移除匹配隊列//為了謹慎,再進行一次判斷if (session1 == null){//玩家1不在線就把玩家2放回到匹配隊列中matchQueue.offer(player2);return;}if (session2 == null){//玩家2不在線就把玩家1放回到匹配隊列中matchQueue.offer(player1);return;}//TODO 把這兩個玩家放到一個游戲房間中Room room = new Room();roomManager.add(room,player1.getUserId(),player2.getUserId());//給玩家反饋信息 匹配到了MatchResponse response1 = new MatchResponse();response1.setOk(true);response1.setMessage("matchSuccess");session1.sendMessage(new TextMessage(objectMapper.writeValueAsString(response1)));MatchResponse response2 = new MatchResponse();response2.setOk(true);response2.setMessage("matchSuccess");session2.sendMessage(new TextMessage(objectMapper.writeValueAsString(response2)));}catch (IOException | InterruptedException e){e.printStackTrace();}
public void add(User user){if (user.getScore() < 2000){synchronized (normalQueue){normalQueue.offer(user);normalQueue.notify();}System.out.println("把玩家"+user.getUsername()+"加入到了 normalQueue 中!");} else if (user.getScore() >= 2000 && user.getScore() < 3000) {synchronized (highQueue){highQueue.offer(user);highQueue.notify();}System.out.println("把玩家"+user.getUsername()+"加入到了 highQueue 中!");}else {synchronized (veryHighQueue){veryHighQueue.offer(user);veryHighQueue.notify();}System.out.println("把玩家"+user.getUsername()+"加入到了 veryHighQueue 中!");}}
public void remove(User user){if (user.getScore() < 2000){synchronized (normalQueue){normalQueue.remove(user);}System.out.println("把玩家"+user.getUsername()+"移除normalQueue隊列");} else if (user.getScore() >= 2000 && user.getScore() < 3000) {synchronized (highQueue){highQueue.remove(user);}System.out.println("把玩家"+user.getUsername()+"移除highQueue隊列");}else {synchronized (veryHighQueue){veryHighQueue.remove(user);}System.out.println("把玩家"+user.getUsername()+"移除veryHighQueue隊列");}}
ObjectMapper來處理json
MatchResponse response = new MatchResponse();response.setOk(false);response.setReason("您尚未登錄! 不能進行匹配!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));//TextMessage--一個文本格式的websocket數據 通過ObjectMapper把對象轉成JSON字符串!
針對多開情況的判定
// 獲取玩家身份信息(哪個玩家在游戲大廳中 建立了連接)// (注意!!!可能出現玩家身份信息為空的現象----玩家直接通過 /game_hall.html 進入游戲大廳try {User user = (User) session.getAttributes().get("user");//判斷當前用戶是否已經登錄,禁止多開WebSocketSession webSocketSession = onlineUserManager.getFromGameHall(user.getUserId());if( webSocketSession != null || onlineUserManager.getFromGameRoom(user.getUserId()) != null){MatchResponse response = new MatchResponse();response.setOk(true);response.setReason("此用戶已登錄! 禁止重復登錄!");response.setMessage("repeatConnection");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));//session.close();//連接斷開return;}
//判斷用戶有沒有多開if (onlineUserManager.getFromGameHall(user.getUserId()) != null|| onlineUserManager.getFromGameRoom(user.getUserId()) !=null){response.setOk(false);response.setReason("禁止多開游戲");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));return;}
success: function(data) { // bodyconsole.log(JSON.stringify(data));if (data && data.userId > 0) {// 登錄成功, 跳轉到游戲?廳alert("登錄成功!")location.assign('/game_hall.html');} else {alert("登錄失敗! 用戶名密碼錯誤! 或者 該賬號正在游戲中!");}}
下線的時候注意針對多開情況的判定,避免錯誤刪除玩家
try {//玩家下線,移除User user = (User) session.getAttributes().get("user");// 把該玩家設置為下線狀態// 避免移除多開情況時,錯誤刪除WebSocketSession webSocketSession = onlineUserManager.getFromGameHall(user.getUserId());if (webSocketSession == session){onlineUserManager.exitGameHall(user.getUserId());}matcher.remove(user);//玩家正在匹配中,連接斷開,移除玩家}
處理連接關閉、異常(玩家中途退出)后的不合理操作
此時連接已經關閉,不應該再發送信息給客戶端
catch (NullPointerException e){e.printStackTrace();//連接已經關閉,不應該再發送信息給客戶端
// //把 當前用戶未登錄 這個信息返回回去
// MatchResponse response = new MatchResponse();
// response.setOk(false);
// response.setReason("您尚未登錄! 不能進行匹配!");
// session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
// //TextMessage--一個文本格式的websocket數據 通過ObjectMapper把對象轉成JSON字符串!}
用戶操作不可控,謹慎處理可能發生的情況
WebSocketSession existSession = onlineUserManager.getFromGameRoom(user.getUserId());if (existSession != session){System.out.println("當前的會話不是玩家游戲中的會話, 不做處理!");return;}
游戲大廳數據更新
對局結束后,分數、對局數會發生改變,因此游戲大廳中的數據需要從數據庫中獲取
public Object getUserInfo(HttpServletRequest req){try {HttpSession httpSession = req.getSession(false);User user = (User) httpSession.getAttribute("user");//去數據庫中找最新的數據(一輪比賽后數據會有變化User newUser = userMapper.selectByName(user.getUsername());return newUser;}catch (NullPointerException e){return new User();}