[网页五子棋][匹配模块]服务器开发、用户管理器(创建匹配请求/响应对象、处理连接成功、处理下线)
文章目录
- 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))); }
}