SpringBoot - 整合WebSocket及STOMP部分API簡介

WebSocket簡介

WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。

WebSocket是一個消息架構,不強制使用任何特定的消息協議,它依賴於應用層解釋消息的含義;
與處在應用層的HTTP不同,WebSocket處在TCP上非常薄的一層,會將字節流轉換爲文本/二進制消息,因此,對於實際應用來說,WebSocket的通信形式層級過低,因此,可以在 WebSocket 之上使用STOMP協議,來爲瀏覽器和server間的通信增加適當的消息語義。

理解STOMP和WebSocket之間的關係

1、直接使用WebSocket(SockJS)就很類似於使用TCP套接字來編寫web應用,因爲沒有高層協議,就需要我們定義應用間所發送消息的語義,還需要確保連接的兩端都能遵循這些語義。
2、同HTTP在TCP套接字上添加請求-響應模型層一樣,STOMP在WebSocket之上提供了一個基於幀的線路格式層,用來定義消息語義。

消息羣發 - 發佈模式

一、相關依賴引入

1、後端pom依賴
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、前端依賴
<!-- 前端庫 -->
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>sockjs-client</artifactId>
	<version>1.1.2</version>
</dependency>
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>stomp-websocket</artifactId>
	<version>2.3.3</version>
</dependency>
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>jquery</artifactId>
	<version>3.3.1</version>
</dependency>

也可以直接前端頁面引入相關JS文件

jquery的js
sockjs-client的js
stomp-websocket的js

二、WebSocket配置文件

Spring提供了基於WebSocketSTOMP支持,STOMP是一個簡單的可互操作的協議,通常用於中間服務器與客戶端之間進行異步消息傳遞

@Configuration
@EnableWebSocketMessageBroker    // 開啓WebSocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        /**
         * 設置消息代理的前綴 - '/topic'
         * 被設置的前綴的消息會被轉發到消息代理
         * 消息代理再將消息廣播給當前連接的客戶端 - 羣發
         */
        registry.enableSimpleBroker("/topic");
        /**
         * 配置目標前綴,這裏只配置一個,即/app
         * 配置了的前綴爲/app可以通過@MessageMapping註解的方法處理
         * 其他的destination如/topic、/queue將被直接交給broker處理
         */
        registry.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        /**
         * 設置前綴爲/chat,可以通過這個/chat建立連接
         * withSockJs支持解決WbeSocket兼容問題
         */
        registry.addEndpoint("/chat").withSockJS();
    }
}

三、創建消息Pojo類

@Data
public class Message {
    /** 暱稱 */
    private String name;
    /** 消息內容 */
    private String content;
}

三、Controller層

這裏的Controller主要是用來處理消息的,前面配置文件中,我們配置了/app目標前綴,前綴爲/app的會進入到我們下面所說的Controller層裏標註@MessageMapping的方法裏被處理。

@Controller
public class TestController {
    @MessageMapping("/hello")          // 接收/app/hello路徑發來的消息
    @SendTo("/topic/greetings")        // 轉發到/topic/greetings
    public Message greeting(Message message){
        return message;
    }
}

除了@SendTo註解可以將處理過的消息轉發到broker,再由broker進行消息廣播外。Spring提供了一個SimpMessagingTemplate類來讓開發者更加靈活地發送消消息。

@Autowired
private SimpMessagingTemplate template;

@MessageMapping("/hello")          // 接收/app/hello路徑發來的消息
public void greeting(Message message){
    template.convertAndSend("/topic/greetings",message);
}

四、前端界面及接受和發送消息

1、前端頁面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket羣聊</title>
</head>
<body>
<!-- 用戶名區域 -->
<div>
    <label for="name">請輸入用戶名:</label>
    <input type="text" id="name" placeholder="用戶名">
</div>
<!-- 連接區域 -->
<div>
    <button id="connect" type="button">連接</button>
    <button id="disconnect" type="button" disabled="disabled">斷開連接</button>
</div>
<!-- 發送消息區域 -->
<div id="chat" style="display:none">
    <div>
        <label for="content">請輸入聊天內容:</label>
        <input type="text" id="content" placeholder="聊天內容">
    </div>
    <button id="send" type="button">發送</button>
</div>
<!-- 聊天區域 -->
<div id="greetings">
    <div id="conversation" style="display: none">羣聊中...</div>
</div>
<!-- JS引用區域 -->
<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
<!-- 自定義JS -->
<script src="/js/app.js"></script>
</body>
</html>
2、自定義JS
// STOMP客戶端
var stompClient = null;
/**
 * 是否已經連接,對頁面顯示進行處理
 * @param connected 是否已經連接
 */
function setConnected(connected) {
    $("#connect").prop("disabled",connected);
    $("#disconnect").prop("disabled",!connected);
    if(connected){
        $("#conversation").show();
        $("#chat").show();
    }else{
        $("#conversation").hide();
        $("#chat").hide();
    }
    $("#greetings").html("");
}

/**
 * 建立WebSocket連接
 */
function connect() {
    // 如果名字爲空,則不讓連接
    if(!$("#name").val()){
        return;
    }
    // 通過SockJs建立連接對象
    var socket = new SockJS('/chat');

    // 也可以通過WebSocket建立連接
    // var socket = new WebSocket("/chat");

    // 獲取STOMP子協議的客戶端對象
    stompClient = Stomp.over(socket);
    // 向服務器發起websocket連接併發送CONNECT鎮
    stompClient.connect({},function (frame) {
        // 表示連接成功,
        setConnected(true);
        // 訂閱服務端發送的消息
        stompClient.subscribe('/topic/greetings',function (greeting) {
            // 顯示消息
            showGreeting(JSON.parse(greeting.body));
        });
    });
}

/**
 * 斷開連接
 */
function disconnect() {
    if (stompClient != null){
        stompClient.disconnect();
    }
    setConnected(false);
}

/**
 * 發送消息
 */
function sendMessage() {
    // 發送消息
    stompClient.send("/app/hello",{},
        JSON.stringify({'name':$("#name").val(),'content':$("#content").val()}));
}

/**
 * 控制顯示消息
 * @param message 消息對象
 */
function showGreeting(message) {
    $("#greetings").append("<div>" + message.name + ":" + message.content + "</div>");
}

$(function () {
   $("#connect").click(function () {
       connect();
   });
   $("#disconnect").click(function () {
       disconnect();
   });
   $("#send").click(function () {
       sendMessage();
       // 清空聊天框
       $("#content").val("");
   })
});

五、測試

運行項目,用兩個不同的瀏覽器打開,分別發送消息。
消息

STOMP中的API介紹

app.js中的STOMP的API解釋。

一、發起連接

1、Socket連接對象
/**
 * 通過SockJS建立WebSocket連接對象
 * 參數就是我們配置文件中registerStompEndpoints方法裏配置的前綴
 */
var socket = new SockJS('/chat');
// 同樣,可以用下面這行代碼代替,但是就沒有SockJS提供的兼容支持
var socket = new WebSocket("/chat");
2、簽名方法
// 獲取STOMP子協議的客戶端對象
var stompClient = Stomp.over(socket);
/**
 * headers:客戶端的認證信息
 * connectCallback:連接成功時(服務器響應 CONNECTED 幀)的回調方法
 * errorCallback:連接失敗時(服務器響應 CONNECTED 幀)的回調方法
 * 如果不需要認證,使用{}替代即可
 * 失敗回調方法可以省略
 */
stompClient.connect(headers, connectCallback, errorCallback);

其中headers認證信息大概長這個樣子:

var headers = {
  login: 'mylogin',
  passcode: 'mypasscode',
  // additional header
  'client-id': 'my-client-id'
};

二、斷開連接

斷開連接是異步操作的。

/**
 * disconnectCallback:回調方法
 * 回調方法在操作完成時調用,可以省略
 */
stompClient.disconnect(disconnectCallback);

三、心跳機制

STOMP 1.1 版本,默認開啓了心跳檢測機制,可通過client對象的heartbeat進行配置,默認是10000ms

stompClient.heartbeat.outgoing=20000;
stompClient.heartbeat.incoming=0;

四、發送消息

/**
 * destinationUrl:服務端Controller中@MessageMapping中匹配的URL
 * headers:發送信息的header,JavaScript對象,可選參數,省略可用{}代替
 * body:發送信息的body,字符串,可選參數
 */
stompClient.send(destinationUrl, headers, body);
1、JSON支持

body消息可以使用JSON數據,使用JSON.stringify轉換即可

stompClient.send("/app/hello",{},
        JSON.stringify({'name':$("#name").val(),'content':$("#content").val()}));
2、事務支持

STOMP 客戶端支持在發送消息時用事務進行處理

// 該方法返回一個包含了事務 id、commit()、abort()的JavaScript 對象
var tx = stompClient.begin();
// 在headers對象中加入事務id,若沒有添加,則會直接發送消息,不會以事務進行處理
stompClient.send("/app/hello",{transaction: tx.id},
        JSON.stringify({'name':$("#name").val(),'content':$("#content").val()}));
// 提交事務
tx.commit();

五、訂閱和接收消息(含取消訂閱和接收消息)

1、訂閱和接收
/**
 * destinationUrl:服務端@SendTo匹配的URL
 * callback:爲每次收到服務器推送的消息時的回調方法,該方法包含參數message(即收到的消息)
 * headers:附加的headers,JavaScript對象,可選參數
 * headers方法返回一個包含了id屬性的JavaScript對象,可作爲unsubscribe()方法的參數
 */
var subscription = varstompClient.subscribe(destinationUrl, callback, headers);
2、取消訂閱和接收
subscription.unsubscribe();

六、瞭解更多

瞭解更多,可以百度,或是參考STOMP 客戶端 API 整理

消息單發 - 點對點模式

爲了更能表現出點對點的用戶概念,所以這裏引入SpringSecurity,然後通過內存裏的賬號登錄。

一、引入SpringSecurity依賴並配置

1、引入依賴
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、配置文件
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                .withUser("lcy")
                .password("$2a$10$XcigeMfToGQ2bqRToFtUi.sG1V.HhrJV6RBjji1yncXReSNNIPl1K")  // 123
                .roles("admin")
                .and()
                .withUser("jyqc")
                .password("$2a$10$XcigeMfToGQ2bqRToFtUi.sG1V.HhrJV6RBjji1yncXReSNNIPl1K")
                .roles("user")
                .and()
                .withUser("xbyx")
                .password("$2a$10$XcigeMfToGQ2bqRToFtUi.sG1V.HhrJV6RBjji1yncXReSNNIPl1K")
                .roles("admin");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()   // 登錄就可以訪問
                .and()
                .formLogin().permitAll();       // 登錄相關url都可以訪問
    }
}

二、修改WebSocket配置文件以及Controller

1、WebSocket配置文件
@Configuration
@EnableWebSocketMessageBroker    // 開啓WebSocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic","/queue");
        registry.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat").withSockJS();
    }
}
2、Controller

新增如下代碼

@Autowired
private SimpMessagingTemplate template;

@MessageMapping("/chat")
public void chat(Principal principal, Chat chat){
    String from = principal.getName();
    chat.setFrom(from);
    // convertAndSendToUser內部會做處理
    // 發送的最終路徑是/user/用戶名/queue/chat
    template.convertAndSendToUser(chat.getTo(),"/queue/chat",chat);
}

convertAndSendToUser源碼查看,可以發現裏面對url進行修改,this.destinationPrefix默認是/user
在這裏插入圖片描述

三、HTML與JS

1、html頁面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket單聊</title>
</head>
<body>
<div id="chat">
    <div id="chatsContent">
    </div>
    <div>
        聊天內容:
        <input type="text" id="content" placeholder="請輸入聊天內容"><br>
        目標用戶:
        <input type="text" id="to" placeholder="請輸入目標用戶"><br>
        <button id="send" type="button">發送</button>
    </div>
</div>
<!-- JS引用區域 -->
<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
<script src="/js/chat.js"></script>
</body>
</html>
2、chat.js
// STOMP客戶端
var stompClient = null;

function connect() {
    var socket = new SockJS("/chat");
    stompClient = Stomp.over(socket);
    stompClient.connect({},function (frame) {
        // 因爲後端的convertAndSendToUser處理了url,所以也就是爲什麼相比之前的羣裏多了個/user的原因
        stompClient.subscribe('/user/queue/chat',function (chat) {
            showGreeting(JSON.parse(chat.body));
        });
    });
}

function sendMsg() {
    stompClient.send("/app/chat",{},JSON.stringify({'content':$("#content").val(),'to':$("#to").val()}));
}

function showGreeting(message) {
    $("#chatsContent")
        .append("<div>" + message.from + ":" + message.content + "</div>");
}

$(function () {
    connect();
    $("#send").click(function () {
        sendMsg();
        $("#content").val("");
    })
});

測試

開啓三個不同的瀏覽器測試,點對點模式沒有問題。
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章