文章目錄
- MatchAPI 類
- 用戶管理器
- 創建匹配請求/響應對象
- 處理連接成功—afterConnectionEstablished
- 處理下線——handleTransportError/afterConnectionClosed
MatchAPI 類
創建 api.MatchAPI
,繼承自 TextWebSocketHandler
作為處理 WebSocket
請求的入口類
- 準備好一個
ObjectMapper
,后續用來處理JSON
數據
package org.example.java_gobang.api; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler; //通過這個類,來處理匹配功能中的 websocket 請求
@Component
public class MatchAPI extends TextWebSocketHandler { // 稍后處理 JSON 會用到的對象 private ObjectMapper objectMapper = new ObjectMapper(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { }
}
修改 config.WebSocketConfig
,把 MatchAPI
加進去
- 在
addHandler
之后,再加上一個.addInterceptors(new HttpSessionHandshakeInterceptor())
代碼 - 這樣可以把之前登錄過程往
HttpSession
中存放的數據(主要是User
對象),放到WebSocket
的Session
中,方便后續獲取到當前用戶的信息
在注冊
websocket API
的時候,就需要把前面準備好的HttpSession
給搞過來(搞到WebSocket
的Session
中)
- 用戶登錄就會給
HttpSession
中保存用戶的信息- 你點了一下匹配按鈕,就需要告訴服務器當前是誰在點按鈕
package org.example.java_gobang.config; import org.example.java_gobang.api.MatchAPI;
import org.example.java_gobang.api.TestAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; /** * 這個類是用來注冊 WebSocketHandler 的配置類 * 這個類是來告訴 Spring(websocket),哪一個類是和哪一個路徑相匹配的 */
@Configuration
@EnableWebSocket // 讓 Spring 框架知道這個類是用來配置 websocket 的
public class WebSocketConfig implements WebSocketConfigurer { @Autowired private TestAPI testAPI; @Autowired private MatchAPI matchAPI; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) { // 當我們的客戶端連接到 /test 路徑的時候,就會觸發到當前的 TestAPI,然后調用執行里面的方法 webSocketHandlerRegistry.addHandler(testAPI, "/test"); webSocketHandlerRegistry.addHandler(matchAPI,"/findMatch") .addInterceptors(new HttpSessionHandshakeInterceptor()); }
}
- 通過
.addInterceptors(new HttpSessionHandshakeInterceptor()
這個操作來把HttpSession
?的屬性放到WebSocket
的session
中 - 然后就可以在
WebSocket
代碼中WebSocketSession
?拿到HttpSession
中的attribute
用戶管理器
此處我們需要能夠保存和表示用戶的上線狀態
之所以要維護用戶的在線狀態,目的是為了能夠在代碼中比較方便的獲取到某個用戶當前的 websocket
會話
- 從而可以通過這個會話來給客戶端發送信息
- 同時也可以感知到他們的在線/離線狀態
可以使用一個哈希表來保存當前用戶的在線狀態
key
就是用戶id
value
就是用戶當前使用的websocket
會話
創建 game.OnlineUserManager
類,借助這個類, ???可以判定??是否是在線, 同時也可以進??便的獲取到 Session
從?給客?端回話
- 當玩家建立好
websocket
連接,則將鍵值對加入OnlineUserManager
中 - 當玩家斷開
websocket
連接,則將鍵值對從OnlineUserManager
中刪除 - 在玩家連接好的過程中,隨時可以通過
userId
來查詢對應的會話,以便向客戶端返回數據
package org.example.java_gobang.game; import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession; import java.util.HashMap; @Component
public class OnlineUserManager { // 這個哈希表就用來表示當前用戶在游戲大廳的在線狀態 private HashMap<Integer, WebSocketSession> gameHall = new HashMap<>(); public void enterGameHall(int userId, WebSocketSession webSocketSession) { gameHall.put(userId, webSocketSession); } public void exitGameHall(int userId) { gameHall.remove(userId); } public WebSocketSession getFromGameHall(int userId) { return gameHall.get(userId); }
}
創建匹配請求/響應對象
創建 game.MatchRequest
類
package org.example.java_gobang.game; // 這是表示一個 websocket 的匹配請求
public class MatchRequest { private String message = ""; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; }
}
創建 game.MatchResponse
類
package org.example.java_gobang.game; // 這是表示一個 websocket 的匹配響應
public class MatchResponse { private boolean ok; private String reason; private String message; public boolean isOk() { return ok; } public void setOk(boolean ok) { this.ok = ok; } public String getReason() { return reason; } public void setReason(String reason) { this.reason = reason; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; }
}
處理連接成功—afterConnectionEstablished
實現 afterConnectionEstablished
?法
- 通過參數中的
session
對象,拿到之前登錄時設置的User
信息 - 使用
onlineUserManager
來管理用戶的在線狀態 - 先判定用戶是否是已經在線,如果在線則直接返回出錯(禁止一個賬號多開)
- 設置玩家的上線狀態
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 玩家上線,加入到 OnlineUserManager 中 // 1. 獲取到當前用戶的身份信息(誰在游戲大廳中建立的連接) // 此處的代碼,之所以能夠 getAttributes,全靠了在注冊 WebSocket 的時候, // 加上的 .addInterceptors(new HttpSessionHandshakeInterceptor()); // 這個邏輯就把 HttpSession 中的 Attributes 都給拿到 WebSocketSession 中了 // 在 Http 登錄邏輯中,往 HttpSession 中存了 User 數據: (UserAPI)httpSession.setAttribute("user", user); // 此時就可以在 WebSocketSession 中把之前 HttpSession 里存的 User 給拿到了 // 此處我們拿到的 user,是有可能為空的! // 如果之前用戶壓根就沒有通過 HTTP 來進行登錄,直接就通過 /game_hall.html 這個 URL 來訪問游戲大廳頁面 // 此時就會出現 user 為空的情況(繞開登錄界面就會為空) try { User user = (User) session.getAttributes().get("user"); // 2. 拿到了身份信息之后,就可以把玩家設成在線狀態 onlineUserManager.enterGameHall(user.getUserId(), session); System.out.println("玩家 " + user.getUsername() + "進入游戲大廳"); }catch (NullPointerException e) { // 出現空指針異常,說明當前用戶的身份是空的,用戶未登錄 // 把當前用戶尚未登錄這個信息給返回回去 MatchResponse response = new MatchResponse(); response.setOk(false); response.setReason("您未登錄! 不能進行后續匹配!"); session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response))); }
session.sendMessage
- 先通過
ObjectMapper
把MatchResponse
對象轉成JSON
字符串 - 然后再包裝上一層
TextMessage
,再進行傳輸 TestMessage
就表示一個文本格式的websocket
數據包
- 先通過
處理下線——handleTransportError/afterConnectionClosed
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { try { // 玩家下線,從 OnlineUserManager 中刪除 User user = (User) session.getAttributes().get("user"); 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))); }
} @Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { try { // 玩家下線,從 OnlineUserManager 中刪除 User user = (User) session.getAttributes().get("user"); 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))); }
}