在SpringBoot中实现WebSocket会话管理的方案
作者:一只爱撸猫的程序猿
场景设定
假设我们正在开发一个在线聊天应用,该应用需要实现以下功能:
- 用户可以通过 WebSocket 实时发送和接收消息。
- 系统需要跟踪用户的会话状态,以便在用户重新连接时恢复状态。
- 为了提高效率和安全性,我们需要监控空闲连接并及时关闭它们。
基于这个场景,我们将探讨四种实现 WebSocket 会话管理的策略:
1. 使用现有的会话标识符
一种常见的做法是利用 HTTP 会话(例如,通过 cookies)来管理 WebSocket 会话。
实现方法:
- 在 WebSocket 握手阶段,从 HTTP 请求中提取会话标识符。
- 将 WebSocket 会话与提取的会话标识符关联。
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; import javax.servlet.http.HttpSession; import java.util.Map; public class MyHandshakeInterceptor extends HttpSessionHandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; HttpSession session = servletRequest.getServletRequest().getSession(); attributes.put("HTTP_SESSION_ID", session.getId()); } return super.beforeHandshake(request, response, wsHandler, attributes); } }
这个拦截器需要在 WebSocket 的配置类中注册。例如,在 WebSocketConfig
类中,你可以这样注册拦截器:
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; @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new MyWebSocketHandler(), "/ws") .addInterceptors(new MyHandshakeInterceptor()) .setAllowedOrigins("*"); // 你也可以添加 .withSockJS() 如果你需要SockJS支持 } // ...其他配置... }
2. 自定义协议消息
另一种方法是在 WebSocket 连接中定义自己的消息格式,包含会话管理信息。
实现方法:
- 定义消息格式(如 JSON),包含会话信息。
- 在连接建立后,通过 WebSocket 发送和接收这些自定义消息。
@Controller public class WebSocketController { @Autowired private WebSocketSessionManager sessionManager; @MessageMapping("/sendMessage") public void handleSendMessage(ChatMessage message, SimpMessageHeaderAccessor headerAccessor) { String sessionId = (String) headerAccessor.getSessionAttributes().get("HTTP_SESSION_ID"); // 使用 sessionId 处理消息 // 可以通过 sessionManager 获取用户信息 } // ...其他消息处理方法... }
3. 连接映射
将每个 WebSocket 连接映射到特定的用户会话。
实现方法:
- 在连接建立时,从 WebSocket 握手信息中获取用户身份。
- 维护一个映射,关联 WebSocket 会话 ID 和用户会话。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.util.Iterator; import org.springframework.stereotype.Component; import java.util.concurrent.ConcurrentHashMap; import java.util.Map; @Component public class WebSocketSessionManager extends TextWebSocketHandler { @Autowired private WebSocketHandler webSocketHandler; private Map<String, String> sessionMap = new ConcurrentHashMap<>(); private Map<String, Long> lastActiveTimeMap = new ConcurrentHashMap<>(); public void registerSession(String websocketSessionId, String userSessionId) { sessionMap.put(websocketSessionId, userSessionId); lastActiveTimeMap.put(websocketSessionId, System.currentTimeMillis()); } public String getUserSessionId(String websocketSessionId) { return sessionMap.get(websocketSessionId); } public void updateLastActiveTime(String websocketSessionId) { lastActiveTimeMap.put(websocketSessionId, System.currentTimeMillis()); } public Long getLastActiveTime(String websocketSessionId) { return lastActiveTimeMap.get(websocketSessionId); } public void checkAndCloseInactiveSessions(long timeout) { long currentTime = System.currentTimeMillis(); lastActiveTimeMap.entrySet().removeIf(entry -> { String sessionId = entry.getKey(); long lastActiveTime = entry.getValue(); if (currentTime - lastActiveTime > timeout) { closeSession(sessionId); // 关闭会话 sessionMap.remove(sessionId); // 从用户会话映射中移除 return true; // 从活跃时间映射中移除 } return false; }); } private void closeSession(String websocketSessionId) { // 逻辑来关闭 WebSocket 会话 // 可能需要与 webSocketHandler 交互 } public void unregisterSession(String websocketSessionId) { sessionMap.remove(websocketSessionId); } // 可以添加注销会话的方法等 }
4. 心跳和超时机制
实现心跳消息和超时机制,以管理会话的生命周期。
实现方法:
- 客户端定时发送心跳消息。
- 服务端监听这些消息,并实现超时逻辑。
function sendHeartbeat() { if (stompClient && stompClient.connected) { stompClient.send("/app/heartbeat", {}, JSON.stringify({ timestamp: new Date() })); } } setInterval(sendHeartbeat, 10000); // 每10秒发送一次心跳
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.stereotype.Controller; @Controller public class HeartbeatController { @Autowired private WebSocketSessionManager sessionManager; @MessageMapping("/heartbeat") public void handleHeartbeat(HeartbeatMessage message, SimpMessageHeaderAccessor headerAccessor) { String websocketSessionId = headerAccessor.getSessionId(); sessionManager.updateLastActiveTime(websocketSessionId); // 根据需要处理其他逻辑 } }
使用 Spring 的定时任务功能来定期执行会话超时检查,ScheduledTasks
类中的 checkInactiveWebSocketSessions
方法每5秒执行一次,调用 WebSocketSessionManager
的 checkAndCloseInactiveSessions
方法来检查和关闭超时的会话。
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @EnableScheduling @Component public class ScheduledTasks { @Autowired private WebSocketSessionManager sessionManager; // 定义超时阈值,例如30分钟 private static final long TIMEOUT_THRESHOLD = 30 * 60 * 1000; @Scheduled(fixedRate = 5000) // 每5秒执行一次 public void checkInactiveWebSocketSessions() { sessionManager.checkAndCloseInactiveSessions(TIMEOUT_THRESHOLD); } }
补充:在 WebSocket 连接关闭或用户注销时,可以调用 unregisterSession
方法来清理会话信息。当 WebSocket 连接关闭时,afterConnectionClosed
方法会被调用,这时我们可以通过 sessionManager
移除对应的会话信息。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; public class MyWebSocketHandler extends TextWebSocketHandler { @Autowired private WebSocketSessionManager sessionManager; @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { String websocketSessionId = session.getId(); sessionManager.unregisterSession(websocketSessionId); // 进行其他清理工作 } // 实现其他必要的方法 }
总结
实现 WebSocket 会话管理需要综合考虑应用的需求和架构特点。Spring Boot 提供了实现这些功能的强大支持,但正确地应用这些工具和策略是成功的关键。通过本文的讨论,我们看到了如何在一个实际场景中一步步地思考和实现有效的 WebSocket 会话管理。
以上就是在SpringBoot中实现WebSocket会话管理的方案的详细内容,更多关于SpringBoot实现WebSocket会话的资料请关注脚本之家其它相关文章!