SpringBoot 實戰 (十六) | 整合 WebSocket 基於 STOMP 協議實現廣播訊息

一個優秀的廢人發表於2019-03-04

前言

如題,今天介紹的是 SpringBoot 整合 WebSocket 實現廣播訊息。

什麼是 WebSocket ?

WebSocket 為瀏覽器和伺服器提供了雙工非同步通訊的功能,即瀏覽器可以向伺服器傳送資訊,反之也成立。

WebSocket 是通過一個 socket 來實現雙工非同步通訊能力的,但直接使用 WebSocket ( 或者 SockJS:WebSocket 協議的模擬,增加了當前瀏覽器不支援使用 WebSocket 的相容支援) 協議開發程式顯得十分繁瑣,所以使用它的子協議 STOMP。

STOMP 協議簡介

它是高階的流文字定向訊息協議,是一種為 MOM (Message Oriented Middleware,面向訊息的中介軟體) 設計的簡單文字協議。

它提供了一個可互操作的連線格式,允許 STOMP 客戶端與任意 STOMP 訊息代理 (Broker) 進行互動,類似於 OpenWire (一種二進位制協議)。

由於其設計簡單,很容易開發客戶端,因此在多種語言和多種平臺上得到廣泛應用。其中最流行的 STOMP 訊息代理是 Apache ActiveMQ。

STOMP 協議使用一個基於 (frame) 的格式來定義訊息,與 Http 的 request 和 response 類似 。

廣播

接下來,實現一個廣播訊息的 demo。即服務端有訊息時,將訊息傳送給所有連線了當前 endpoint 的瀏覽器。

準備工作

  • SpringBoot 2.1.3
  • IDEA
  • JDK8

Pom 依賴配置

<dependencies>
        <!-- thymeleaf 模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- web 啟動類 -->
        <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>
        <!-- test 單元測試 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
複製程式碼

程式碼註釋很詳細,不多說。

配置 WebSocket

實現 WebSocketMessageBrokerConfigurer 介面,註冊一個 STOMP 節點,配置一個廣播訊息代理

@Configuration
// @EnableWebSocketMessageBroker註解用於開啟使用STOMP協議來傳輸基於代理(MessageBroker)的訊息,這時候控制器(controller)
// 開始支援@MessageMapping,就像是使用@requestMapping一樣。
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //註冊一個 Stomp 的節點(endpoint),並指定使用 SockJS 協議。
        registry.addEndpoint("/endpointNasus").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 廣播式配置名為 /nasus 訊息代理 , 這個訊息代理必須和 controller 中的 @SendTo 配置的地址字首一樣或者全匹配
        registry.enableSimpleBroker("/nasus");
    }
}
複製程式碼

訊息類

客戶端傳送給伺服器:

public class Client2ServerMessage {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
複製程式碼

伺服器傳送給客戶端:

public class Server2ClientMessage {

    private String responseMessage;

    public Server2ClientMessage(String responseMessage) {
        this.responseMessage = responseMessage;
    }

    public String getResponseMessage() {
        return responseMessage;
    }

    public void setResponseMessage(String responseMessage) {
        this.responseMessage = responseMessage;
    }
}
複製程式碼

演示控制器程式碼

@RestController
public class WebSocketController {

    @MessageMapping("/hello") // @MessageMapping 和 @RequestMapping 功能類似,瀏覽器向伺服器發起訊息,對映到該地址。
    @SendTo("/nasus/getResponse") // 如果伺服器接受到了訊息,就會對訂閱了 @SendTo 括號中的地址的瀏覽器傳送訊息。
    public Server2ClientMessage say(Client2ServerMessage message) throws Exception {
        Thread.sleep(3000);
        return new Server2ClientMessage("Hello," + message.getName() + "!");
    }

}
複製程式碼

引入 STOMP 指令碼

將 stomp.min.js (STOMP 客戶端指令碼) 和 sockJS.min.js (sockJS 客戶端指令碼) 以及 Jquery 放在 resource 資料夾的 static 目錄下。

演示頁面

<!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() {
        // 連線 SockJs 的 endpoint 名稱為 "/endpointNasus"
        var socket = new SockJS('/endpointNasus'); 
        // 使用 STOMP 子協議的 WebSocket 客戶端
        stompClient = Stomp.over(socket); 
        stompClient.connect({}, function(frame) {
            setConnected(true);
            console.log('Connected: ' + frame);
            // 通過 stompClient.subscribe 訂閱 /nasus/getResponse 目標傳送的資訊,對應控制器的 SendTo 定義
            stompClient.subscribe('/nasus/getResponse', function(respnose){
            // 展示返回的資訊,只要訂閱了 /nasus/getResponse 目標,都可以接收到服務端返回的資訊
            showResponse(JSON.parse(respnose.body).responseMessage);
            });
        });
    }
	
	
    function disconnect() {
        // 斷開連線
        if (stompClient != null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log("Disconnected");
    }

    function sendName() {
        // 向服務端傳送訊息
        var name = $('#name').val();
        // 通過 stompClient.send 向 /hello (服務端)傳送資訊,對應控制器 @MessageMapping 中的定義
        stompClient.send("/hello", {}, JSON.stringify({ 'name': name }));
    }

    function showResponse(message) {
          // 接收返回的訊息
          var response = $("#response");
          response.html(message);
    }
</script>
</body>
</html>
複製程式碼

頁面 Controller

注意,這裡使用的是 @Controller 註解,用於匹配 html 字首,載入頁面。

@Controller
public class ViewController {

    @GetMapping("/nasus")
    public String getView(){
        return "nasus";
    }
}
複製程式碼

測試結果

開啟三個視窗訪問 http://localhost:8080/nasus ,初始頁面長這樣:

初始頁面

三個頁面全部點連線,點選連線訂閱 endpoint ,如下圖:

點選連線訂閱 endpoint

點選連線訂閱 endpoint

點選連線訂閱 endpoint

在第一個頁面,輸入名字,點傳送 ,如下圖:

輸入名字,點傳送

在第一個頁面傳送訊息,等待 3 秒,結果是 3 個頁面都接受到了服務端返回的資訊,廣播成功。

第一個頁面結果

第二個頁面結果

第三個頁面結果

原始碼下載:

https://github.com/turoDog/Demo/tree/master/springboot_websocket_demo

如果覺得對你有幫助,請給個 Star 再走唄,非常感謝。

後語

如果本文對你哪怕有一丁點幫助,請幫忙點好看。你的好看是我堅持寫作的動力。

另外,關注之後在傳送 1024 可領取免費學習資料。

資料詳情請看這篇舊文:Python、C++、Java、Linux、Go、前端、演算法資料分享

一個優秀的廢人,給你講幾斤技術

相關文章