SpringBoot_websocket實戰
- 前言
- 1.websocket入門
- 1.1 websocket最小化配置
- 1.1.1 后端配置
- 1.1.2 前端配置
- 1.2 websocket使用sockjs
- 1.2.1 后端配置
- 1.2.2 前端配置
- 1.3 websocket使用stomp協議
- 1.3.1 后端配置
- 1.3.2 前端配置
- 2.websocket進階
- 2.1 websocket與stomp有什么區別
- 2.2 websocket與stomp怎么選
- 3.websocket實戰
- 3.1 案例1:websocket 實現web-ssh
- 3.2 案例2:websocket 實現控制臺日志在線展示
前言
本文記錄說明springboot websocket示例及實戰,你將學習到
三種websocket開發方式
- /websocket 接口演示原生websocket前后端收發websocket消息
- /websocket-sockjs 演示使用sockjs 前后端收發websocket消息
- /weboscket-stomp 演示使用stomp協議使用websocket
以及通過實戰演示在不同業務場景的技術選擇
- web-ssh 使用sockjs點對點數據傳輸
- 日志項目 使用stomp廣播數據
使用環境:
- springboot: 2.3.2.RELEASE
- jdk: java11
1.websocket入門
Spring Boot WebSocket是Spring框架的一部分,用于實現WebSocket通信協議的支持。WebSocket是一種雙向通信協議,允許服務器與客戶端之間進行實時、低延遲的數據交換,適用于實時聊天、實時通知、在線協作和實時數據展示等場景。Spring Boot WebSocket提供了使用WebSocket的簡化和高效的方法,讓開發者能夠輕松地實現WebSocket通信。
以下是Spring Boot WebSocket的主要特點和概念:
- WebSocket協議支持: Spring Boot WebSocket支持WebSocket通信協議,允許雙向的、實時的通信。WebSocket允許服務器主動向客戶端推送數據,而不需要客戶端發起請求。
- STOMP協議支持: 除了原始WebSocket,Spring Boot WebSocket還支持STOMP(Simple Text Oriented Messaging Protocol)協議。STOMP是一個基于文本的協議,它提供了更高級的消息傳遞功能,例如消息訂閱、目標廣播和認證等。
- 消息處理器: Spring Boot WebSocket允許你定義消息處理器,用于處理WebSocket消息。你可以編寫處理器來處理接收到的消息,并決定如何響應。
- 消息代理: Spring Boot WebSocket支持消息代理,你可以配置一個消息代理服務器(如RabbitMQ或ActiveMQ),用于處理WebSocket消息的分發和廣播。這使得構建分布式和擴展的WebSocket應用程序更容易。
- 消息廣播: Spring Boot WebSocket允許你將消息廣播到多個訂閱者。這對于實現群聊、廣播通知和實時數據展示等功能非常有用。
- 安全性: Spring Boot WebSocket提供了與Spring Security集成的方式,可以輕松實現WebSocket連接的安全性和認證。
- SockJS支持: Spring Boot WebSocket還支持SockJS,這是一個JavaScript庫,用于處理瀏覽器不支持WebSocket的情況下的降級處理,使得WebSocket通信在各種瀏覽器上都能正常工作
1.1 websocket最小化配置
步驟說明
- 后端通過@EnableWebSocket注解開啟websocket功能
- 定義websocket的訪問端點 (作用: 定義對外暴露websocket接口)
- 定義websocket的處理器 (作用: 解決websocket建立后消息怎么處理)
- 前端通過new WebSocket(“ws://localhost:9090/websocket”); 打開websocket連接并監聽消息
1.1.1 后端配置
- maven 依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--websocket--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
- WebSocketConfiguration 實現WebSocketConfigurer注冊websocket接口及處理器
@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {@Autowiredprivate EchoWebSocketHandler echoWebSocketHandler;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 配置原生WebSocket處理器registry.addHandler(echoWebSocketHandler, "/websocket").setAllowedOrigins("*"); //允許跨域}@Beanpublic EchoWebSocketHandler echoWebSocketHandler() {return new EchoWebSocketHandler();}
}
- EchoWebSocketHandler 實現WebSocketHandler接口, 接受消息后簡單打印轉發消息
public class EchoWebSocketHandler implements WebSocketHandler {private static final Logger LOGGER = LoggerFactory.getLogger(EchoWebSocketHandler.class);@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// 連接建立時的處理邏輯LOGGER.info("[websocket-echo] session:{} ConnectionEstablished", session.getId());}@Overridepublic void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws IOException {String payload = (String) message.getPayload();LOGGER.info("[websocket-echo] session:{} receive:{}", session.getId(), payload);session.sendMessage(new TextMessage(payload));}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {// 處理傳輸錯誤}// 連接關閉時的處理邏輯@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {LOGGER.info("[websocket-echo] WebSocketSession[{}] close all ssh connection", session.getId());}@Overridepublic boolean supportsPartialMessages() {return false;}
}
1.1.2 前端配置
TODO 說明下WebSocket 對象作用
<!DOCTYPE html>
<html>
<head><title>原始WebSocket示例</title>
</head>
<body>
<h1>測試默認websocket 接發消息</h1>
<div id="messages"></div>
<input type="text" id="message" placeholder="輸入消息">
<button onclick="sendMessage()">發送</button><script>var socket = new WebSocket("ws://localhost:9090/websocket");socket.onopen = function(event) {console.log("WebSocket連接已打開");};socket.onmessage = function(event) {var messages = document.getElementById("messages");messages.innerHTML += "<p>" + event.data + "</p>";};function sendMessage() {var messageInput = document.getElementById("message");var message = messageInput.value;socket.send(message);messageInput.value = "";}
</script>
</body>
</html>
1.2 websocket使用sockjs
為了區別原生WebSocket處理器,以示區別,再注冊一個接口/websocket-sockjs. 并且開啟.withSockJS()
1.2.1 后端配置
@Configuration
@EnableWebSocket
@Import({WebSocketStompConfiguration.class})
public class WebSocketConfiguration implements WebSocketConfigurer {@Autowiredprivate EchoWebSocketHandler echoWebSocketHandler;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 配置原生WebSocket處理器器registry.addHandler(echoWebSocketHandler, "/websocket").setAllowedOrigins("*");//WebSocket connection to 'ws://localhost:9090/websocket' failed: Error during WebSocket handshake: Unexpected response code: 200//.withSockJS();//.withSockJS()的作用是聲明我們想要使用 SockJS 功能,如果WebSocket不可用的話,會使用 SockJS。(前端需要使用sockjs庫)registry.addHandler(echoWebSocketHandler, "/websocket-sockjs").setAllowedOrigins("*").withSockJS();}@Beanpublic EchoWebSocketHandler echoWebSocketHandler() {return new EchoWebSocketHandler();}
}
1.2.2 前端配置
使用sockjs需要引入前端sockjs的庫: https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js
<!DOCTYPE html>
<html>
<head><title>原始WebSocket示例</title><script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
</head>
<body>
<h1>測試websocket sockjs接發消息</h1>
<div id="messages"></div>
<input type="text" id="message" placeholder="輸入消息">
<button onclick="sendMessage()">發送</button><script>// var socket = new WebSocket("ws://localhost:9090/websocket");//原始寫法var socket = new SockJS('http://localhost:9090/websocket-sockjs');// 依賴sockjs庫socket.onopen = function(event) {console.log("WebSocket連接已打開");};// 監聽websockt消息回調socket.onmessage = function(event) {var messages = document.getElementById("messages");messages.innerHTML += "<p>" + event.data + "</p>";};// 定義連接關閉時的回調函數socket.onclose = function () {console.log('WebSocket 連接已關閉');};// 定義連接錯誤時的回調函數socket.onerror = function (e) {console.error('WebSocket 連接出錯:', e);};function sendMessage() {var messageInput = document.getElementById("message");var message = messageInput.value;socket.send(message);messageInput.value = "";}
</script>
</body>
</html>
1.3 websocket使用stomp協議
步驟說明
- 啟用stomp協議, 通過@EnableWebSocketMessageBroker 并注冊stomp的端點
- 定義stomp端點 WebSocketStompConfiguration
- 配置stomp消息路由 TestWebSocketStompController
1.3.1 后端配置
@EnableWebSocketMessageBroker //在 WebSocket 上啟用 STOMP
public class WebSocketStompConfiguration {@Bean@ConditionalOnMissingBeanpublic WebSocketMessageBrokerConfigurer brokerConfigurer() {return new WebSocketMessageBrokerConfigurer() {/*** stomp 協議,一種格式比較簡單且被廣泛支持的通信協議,spring4提供了以stomp協議為基礎的websocket通信實現。* spring 的websocket實現,實際上是一個簡易版的消息隊列(而且是主題-訂閱模式的)* @param registry*/@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {// "/stomp-websocket",客戶端需要注冊這個端點進行鏈接,// .withSockJS()的作用是聲明我們想要使用 SockJS 功能,如果WebSocket不可用的話,會使用 SockJS。registry.addEndpoint("/websocket-stomp").setAllowedOrigins("*").withSockJS();}};}}
- 配置消息轉發路由
@Controller
public class TestWebSocketStompController {private static final Logger LOGGER = LoggerFactory.getLogger(TestWebSocketStompController.class);@AutowiredSimpMessagingTemplate messagingTemplate;@GetMapping("/websocket/print/{msg}")@ResponseBodypublic String print(@PathVariable String msg) {messagingTemplate.convertAndSend("/topic/print", msg);return msg;}@MessageMapping("/app/{destination}") // 使用 {destination} 占位符來捕獲動態的目的地@SendTo("/topic/print") // 服務器廣播消息的目的地public String handleMessage(@DestinationVariable String destination, String message) {// 處理接收到的消息LOGGER.info("Dynamic destination:[{}] Received message:{}", destination, message);// 根據動態目的地執行不同的操作if ("destination1".equals(destination)) {// 處理目的地1的操作} else if ("destination2".equals(destination)) {// 處理目的地2的操作} else {// 直接轉發到對應訂閱地址messagingTemplate.convertAndSend("/topic/" + destination, "copy:" + message);}// 可以根據需要添加更多的條件// 返回響應消息return message;}
}
- 最后通過@Import導入WebSocketStompConfiguration配置
@Configuration
@EnableWebSocket
@Import({WebSocketStompConfiguration.class})
public class WebSocketConfiguration implements WebSocketConfigurer {@Autowiredprivate EchoWebSocketHandler echoWebSocketHandler;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 配置原生WebSocket處理器registry.addHandler(echoWebSocketHandler, "/websocket").setAllowedOrigins("*");//WebSocket connection to 'ws://localhost:9090/websocket' failed: Error during WebSocket handshake: Unexpected response code: 200//.withSockJS();//.withSockJS()的作用是聲明我們想要使用 SockJS 功能,如果WebSocket不可用的話,會使用 SockJS。(前端需要使用sockjs庫)registry.addHandler(echoWebSocketHandler, "/websocket-sockjs").setAllowedOrigins("*").withSockJS();}@Beanpublic EchoWebSocketHandler echoWebSocketHandler() {return new EchoWebSocketHandler();}
}
1.3.2 前端配置
前端依賴sockjs及stomp.min.js的庫
<!DOCTYPE html>
<html>
<head><title>WebSocket Example</title><script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.2/sockjs.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script></head>
<body>
<h1>測試websocket stomp協議接發消息</h1>
<h2>發送示例</h2>
<input type="text" id="sendDestination" placeholder="Enter send destination"><input type="text" id="message" placeholder="Enter your message">
<button onclick="sendMessage()">Send Message</button><hr>
<h2>訂閱示例</h2>
<input type="text" id="listenDestination" placeholder="Enter listen destination">
<button onclick="subscribeToDestination()">Subscribe</button>
<div id="messages"></div><script>var stompClient = null;var listenDestination = null;function connect() {var socket = new SockJS('/websocket-stomp'); // WebSocket端點stompClient = Stomp.over(socket);stompClient.connect({}, function (frame) {console.log('stomp Connected: ' + frame);});}function sendMessage() {var sendDestination = document.getElementById('sendDestination').value; // 獲取發送目的地var message = document.getElementById('message').value;stompClient.send('/app/' + sendDestination, {}, message); // 發送消息到指定的目的地}function subscribeToDestination() {listenDestination = document.getElementById('listenDestination').value; // 獲取監聽目的地stompClient.subscribe('/topic/' + listenDestination, function (message) {displayMessage(message.body);});}function displayMessage(message) {var messagesDiv = document.getElementById('messages');var p = document.createElement('p');p.appendChild(document.createTextNode(message));messagesDiv.appendChild(p);}connect();
</script>
</body>
</html>
2.websocket進階
2.1 websocket與stomp有什么區別
- WebSocket
- WebSocket 是一種通信協議: WebSocket 是一種在Web瀏覽器和服務器之間提供雙向通信的協議。它允許在客戶端和服務器之間創建持久性的連接,以便在連接建立后實時地發送和接收消息。
- 原始的、低級的協議: WebSocket 是一種相對較低級的協議,它允許在兩端之間直接發送原始的消息幀(frames)。這意味著你可以通過WebSocket發送任意的二進制或文本數據。
- 需要自行處理消息格式和路由邏輯: WebSocket 本身并沒有規定消息的格式或路由邏輯。在WebSocket上發送的消息可以是任意格式的數據,你需要自行定義消息的結構和處理邏輯。
- STOMP
- STOMP 是一種消息協議: STOMP 是一種基于文本的簡單消息協議,它定義了客戶端和消息代理(broker)之間如何進行交互的規范。STOMP 通常運行在WebSocket之上,它提供了一種在客戶端和服務器之間進行實時消息通信的方式。
- 面向文本的消息格式: STOMP 消息是文本格式的,它使用類似于HTTP報文的格式來定義消息的頭部和體。這種面向文本的格式使得它易于閱讀和調試。
- 定義了消息的結構和路由邏輯: STOMP 規定了消息的格式和消息目的地的語義。它定義了消息頭部的各種屬性(例如,目的地、消息類型等),并提供了一種簡單的消息路由機制,使得消息能夠被發送到特定的目的地。
2.2 websocket與stomp怎么選
如果你期望WebSocket連接之間是隔離的,不需要共享數據,那么使用原始的WebSocket通信是一個合適的選擇。原始WebSocket通信提供了一種簡單而輕量級的方式,每個連接都是獨立的,不會共享會話數據。
STOMP協議通常用于需要高級消息傳遞功能和消息廣播的場景,其中不同連接之間需要共享數據。如果你不需要這種復雜性,原始WebSocket通信足以滿足需求,而且更容易理解和維護。
因此,你可以根據你的應用程序需求選擇使用原始WebSocket通信或STOMP協議。如果只需要簡單的雙向通信并希望保持連接之間的隔離,原始WebSocket通信通常是一個更直接的選擇
3.websocket實戰
3.1 案例1:websocket 實現web-ssh
springboot實現webssh功能, 使用xterm(前端) + websocket + jsch技術實現。后端主要實現websocket消息與jsch命令收發即可。還在開發中, 篇幅關系,實現過程就不寫了。有需要點贊或留言,開源后再通知。
3.2 案例2:websocket 實現控制臺日志在線展示
web在線查看springboot后臺日志,源碼參考:https://github.com/easycode8/easy-log 的easy-log-web模塊, 代碼量很少。