在SpringBoot中實(shí)現(xiàn)WebSocket會(huì)話管理的方案
場(chǎng)景設(shè)定
假設(shè)我們正在開發(fā)一個(gè)在線聊天應(yīng)用,該應(yīng)用需要實(shí)現(xiàn)以下功能:
- 用戶可以通過(guò) WebSocket 實(shí)時(shí)發(fā)送和接收消息。
- 系統(tǒng)需要跟蹤用戶的會(huì)話狀態(tài),以便在用戶重新連接時(shí)恢復(fù)狀態(tài)。
- 為了提高效率和安全性,我們需要監(jiān)控空閑連接并及時(shí)關(guān)閉它們。
基于這個(gè)場(chǎng)景,我們將探討四種實(shí)現(xiàn) WebSocket 會(huì)話管理的策略:
1. 使用現(xiàn)有的會(huì)話標(biāo)識(shí)符
一種常見(jiàn)的做法是利用 HTTP 會(huì)話(例如,通過(guò) cookies)來(lái)管理 WebSocket 會(huì)話。
實(shí)現(xiàn)方法:
- 在 WebSocket 握手階段,從 HTTP 請(qǐng)求中提取會(huì)話標(biāo)識(shí)符。
- 將 WebSocket 會(huì)話與提取的會(huì)話標(biāo)識(shí)符關(guān)聯(lián)。
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); } }
這個(gè)攔截器需要在 WebSocket 的配置類中注冊(cè)。例如,在 WebSocketConfig
類中,你可以這樣注冊(cè)攔截器:
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. 自定義協(xié)議消息
另一種方法是在 WebSocket 連接中定義自己的消息格式,包含會(huì)話管理信息。
實(shí)現(xiàn)方法:
- 定義消息格式(如 JSON),包含會(huì)話信息。
- 在連接建立后,通過(guò) WebSocket 發(fā)送和接收這些自定義消息。
@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 處理消息 // 可以通過(guò) sessionManager 獲取用戶信息 } // ...其他消息處理方法... }
3. 連接映射
將每個(gè) WebSocket 連接映射到特定的用戶會(huì)話。
實(shí)現(xiàn)方法:
- 在連接建立時(shí),從 WebSocket 握手信息中獲取用戶身份。
- 維護(hù)一個(gè)映射,關(guān)聯(lián) WebSocket 會(huì)話 ID 和用戶會(huì)話。
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); // 關(guān)閉會(huì)話 sessionMap.remove(sessionId); // 從用戶會(huì)話映射中移除 return true; // 從活躍時(shí)間映射中移除 } return false; }); } private void closeSession(String websocketSessionId) { // 邏輯來(lái)關(guān)閉 WebSocket 會(huì)話 // 可能需要與 webSocketHandler 交互 } public void unregisterSession(String websocketSessionId) { sessionMap.remove(websocketSessionId); } // 可以添加注銷會(huì)話的方法等 }
4. 心跳和超時(shí)機(jī)制
實(shí)現(xiàn)心跳消息和超時(shí)機(jī)制,以管理會(huì)話的生命周期。
實(shí)現(xiàn)方法:
- 客戶端定時(shí)發(fā)送心跳消息。
- 服務(wù)端監(jiān)聽這些消息,并實(shí)現(xiàn)超時(shí)邏輯。
function sendHeartbeat() { if (stompClient && stompClient.connected) { stompClient.send("/app/heartbeat", {}, JSON.stringify({ timestamp: new Date() })); } } setInterval(sendHeartbeat, 10000); // 每10秒發(fā)送一次心跳
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); // 根據(jù)需要處理其他邏輯 } }
使用 Spring 的定時(shí)任務(wù)功能來(lái)定期執(zhí)行會(huì)話超時(shí)檢查,ScheduledTasks
類中的 checkInactiveWebSocketSessions
方法每5秒執(zhí)行一次,調(diào)用 WebSocketSessionManager
的 checkAndCloseInactiveSessions
方法來(lái)檢查和關(guān)閉超時(shí)的會(huì)話。
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; // 定義超時(shí)閾值,例如30分鐘 private static final long TIMEOUT_THRESHOLD = 30 * 60 * 1000; @Scheduled(fixedRate = 5000) // 每5秒執(zhí)行一次 public void checkInactiveWebSocketSessions() { sessionManager.checkAndCloseInactiveSessions(TIMEOUT_THRESHOLD); } }
補(bǔ)充:在 WebSocket 連接關(guān)閉或用戶注銷時(shí),可以調(diào)用 unregisterSession
方法來(lái)清理會(huì)話信息。當(dāng) WebSocket 連接關(guān)閉時(shí),afterConnectionClosed
方法會(huì)被調(diào)用,這時(shí)我們可以通過(guò) sessionManager
移除對(duì)應(yīng)的會(huì)話信息。
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); // 進(jìn)行其他清理工作 } // 實(shí)現(xiàn)其他必要的方法 }
總結(jié)
實(shí)現(xiàn) WebSocket 會(huì)話管理需要綜合考慮應(yīng)用的需求和架構(gòu)特點(diǎn)。Spring Boot 提供了實(shí)現(xiàn)這些功能的強(qiáng)大支持,但正確地應(yīng)用這些工具和策略是成功的關(guān)鍵。通過(guò)本文的討論,我們看到了如何在一個(gè)實(shí)際場(chǎng)景中一步步地思考和實(shí)現(xiàn)有效的 WebSocket 會(huì)話管理。
以上就是在SpringBoot中實(shí)現(xiàn)WebSocket會(huì)話管理的方案的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot實(shí)現(xiàn)WebSocket會(huì)話的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 使用Spring Security控制會(huì)話的方法
- springboot?實(shí)現(xiàn)不同context-path下的會(huì)話共享
- SpringBoot?整合?Spring-Session?實(shí)現(xiàn)分布式會(huì)話項(xiàng)目實(shí)戰(zhàn)
- springboot配置請(qǐng)求超時(shí)時(shí)間(Http會(huì)話和接口訪問(wèn))
- SpringBoot實(shí)現(xiàn)Tomcat集群的會(huì)話管理功能
- Spring超出最大會(huì)話數(shù)(Max?sessions?limit?reached:?10000)
相關(guān)文章
MySQL實(shí)現(xiàn)類似Oracle序列的方案
今天小編就為大家分享一篇關(guān)于MySQL實(shí)現(xiàn)類似Oracle序列的方案,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03MySQL無(wú)法讀表錯(cuò)誤的解決方法(MySQL 1018 error)
這篇文章主要為大家詳細(xì)介紹了MySQL無(wú)法讀表錯(cuò)誤的解決方法,MySQL 1018 error如何解決?具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01ubuntu mysql 5.6版本的刪除/安裝/編碼配置文件配置
這篇文章主要介紹了ubuntu mysql 5.6版本的刪除,安裝,編碼配置文件配置,需要的朋友可以參考下2017-06-06設(shè)置MySQLroot賬戶密碼報(bào)錯(cuò)ERROR 1064 (42000): You 
在安裝mysql的時(shí)候,設(shè)置root賬戶密碼出現(xiàn)了ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds..錯(cuò)誤,本文小編給大家介紹了相關(guān)的解決方案,需要的朋友可以參考下2023-12-12MySQL數(shù)據(jù)庫(kù)復(fù)合查詢與內(nèi)外連接圖文詳解
本文詳細(xì)介紹了在SQL中進(jìn)行多表查詢的技術(shù),包括笛卡爾積、自連接、子查詢、內(nèi)連接和外連接等,文章還解釋了union和unionall的區(qū)別,以及如何在from子句中使用子查詢,這些技術(shù)對(duì)于處理復(fù)雜的數(shù)據(jù)庫(kù)查詢非常重要,可以有效地從不同表中提取和組合數(shù)據(jù),需要的朋友可以參考下2024-10-10mysql數(shù)據(jù)庫(kù)批量復(fù)制單條數(shù)據(jù)記錄
在開發(fā)數(shù)據(jù)庫(kù)應(yīng)用時(shí),批量操作是一項(xiàng)常見(jiàn)的需求,無(wú)論是數(shù)據(jù)遷移、備份還是更新,理解如何在MySQL中批量復(fù)制單條數(shù)據(jù)都至關(guān)重要,本文將深入探討這一過(guò)程,并提供代碼示例,幫助你更好地理解這一概念2025-02-02