SpringBoot 整合 WebSocket(topic廣播)
1、什么是WebSocket
WebSocket為游覽器和服務器提供了雙工異步通信的功能,即游覽器可以向服務器發送消息,服務器也可以向游覽器發送消息。WebSocket需游覽器的支持,如IE10、Chrome 13+、Firefox 6+,這對我們現在的游覽器來說都不是問題。
WebSocket是通過一個socket來實現雙工異步通訊能力的。但是直接使用WebSocket(或SockJS:WebSocket協議的模擬,增加了當游覽器不支持WebSocket的時候的兼容支持)協議開發程序顯得特別繁瑣, 我們會使用它的子協議STOMP,它是一個更高級別的協議,STOMP協議使用一個基於幀的格式來定義消息,與HTTP的request和reponse類似(具有類似於@RequestMapping的@MassageMapping)
2、什么是STOMP
STOMP,Streaming Text Orientated Message Protocol,是流文本定向消息協議,是一種為MOM(Message Oriented Middleware,面向消息的中間件)設計的簡單文本協議。
它提供了一個可互操作的連接格式,允許STOMP客戶端與任意STOMP消息代理(Broker)進行交互,類似於OpenWire(一種二進制協議)。由於其設計簡單,很容易開發客戶端,因此在多種語言和多種平台上得到廣泛應用。其中最流行的STOMP消息代理是Apache ActiveMQ。
STOMP協議工作於TCP協議之上,使用了下列命令:
1)、SEND 發送
2)、SUBSCRIBE 訂閱
3)、UNSUBSCRIBE 退訂
4)、BEGIN 開始
5)、COMMIT 提交
6)、ABORT 取消
7)、ACK 確認
8)、DISCONNECT 斷開
3、為什么需要WebSocket
答案很簡單,因為 HTTP 協議有一個缺陷:通信只能由客戶端發起。
舉例來說,我們想了解今天的天氣,只能是客戶端向服務器發出請求,服務器返回查詢結果。HTTP 協議做不到服務器主動向客戶端推送信息。如果想持續從服務的獲取消息,則只能使用輪詢或建立長連接的方法來實現,但是這樣或浪費很多不必要的資源。而webSocket則解決了這個問題,通信可由雙方發起,只需要建立一次連接,服務的端就可以持續從服務端獲得消息。主要用來做消息通知,消息推送等模塊
4、SpringBoot使用 STOMP 消息步驟
1)、添加pom文件依賴
2)、java方式配置websocket stomp
3)、消息實體類
4)、書寫控制層
5)、書寫頁面
5、Pom 依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
6、java方式配置websocket stomp
package com.example.demo.config; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { /** * 配置鏈接端點 * @param registry */ @Override public void registerStompEndpoints(StompEndpointRegistry registry){ registry.addEndpoint("/endpointWisely").withSockJS(); } /** * 配置消息代理 * @param registry */ @Override public void configureMessageBroker(MessageBrokerRegistry registry){ registry.enableSimpleBroker("/topic"); } }
7、消息實體類
package com.example.demo.PoJo; /** * 消息接受 */ public class WiselyMessage { private String name; public String getName(){ return name; } }
package com.example.demo.PoJo; /** * 消息返回 */ public class WiselyResponse { private String responseMessage; public WiselyResponse(String responseMessage){ this.responseMessage = responseMessage; } public String getResponseMessage(){ return responseMessage; } }
8、書寫控制層
package com.example.demo.controller; import com.example.demo.PoJo.WiselyMessage; import com.example.demo.PoJo.WiselyResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; import java.security.Principal; @Controller public class WsController { /** * MessageMapping 類似於 RequestMapping * SendTo 訂閱地址 類似於 訂閱一個URL (我的理解就是 調用了這個方法 在返回的時候會給訂閱該url的地址發送數據) * @param message * @return * @throws Exception */ @MessageMapping("/welcome") @SendTo("/topic/getResponse") public WiselyResponse say(WiselyMessage message) throws Exception { Thread.sleep(3000); return new WiselyResponse("Welcome," + message.getName() + "!"); } }
9、書寫頁面
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>Spring Boot+WebSocket+廣播式</title> </head> <body onload="disconnect()"> <noscript><h2 style="color: #ff0000">貌似你的瀏覽器不支持websocket</h2></noscript> <div> <div> <button id="connect" onclick="connect();">連接</button> <button id="disconnect" disabled="disabled" onclick="disconnect();">斷開連接</button> </div> <div id="conversationDiv"> <label>輸入你的名字</label><input type="text" id="name" /> <button id="sendName" onclick="sendName();">發送</button> <p id="response"></p> </div> </div> <script th:src="@{sockjs.min.js}"></script> <script th:src="@{stomp.min.js}"></script> <script th:src="@{jquery.js}"></script> <script type="text/javascript"> var stompClient = null; function setConnected(connected) { document.getElementById('connect').disabled = connected; document.getElementById('disconnect').disabled = !connected; document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden'; $('#response').html(); } function connect() { var socket = new SockJS('/endpointWisely'); //1 stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { setConnected(true); console.log('Connected: ' + frame); stompClient.subscribe('/topic/getResponse', function(respnose){ //2 showResponse(JSON.parse(respnose.body).responseMessage); }); }); } function disconnect() { if (stompClient != null) { stompClient.disconnect(); } setConnected(false); console.log("Disconnected"); } function sendName() { var name = $('#name').val(); //3 stompClient.send("/welcome", {}, JSON.stringify({ 'name': name })); } function showResponse(message) { var response = $("#response"); response.html(message); } </script> </body> </html>
10、客戶端發送和接收消息圖解