本文是WebSocket的故事系列第二篇,WebSocket的故事系列計劃分五篇,旨在由淺入深的介紹WebSocket以及在Springboot中如何快速構建和使用WebSocket提供的能力。本系列計劃包含以下幾篇文章:html
第一篇,什麼是WebSocket以及它的用途
第二篇,Spring中如何利用STOMP快速構建WebSocket廣播式消息模式
第三篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的消息模式(1)
第四篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的消息模式(2)
第五篇,Springboot中,實現網頁聊天室之自定義WebSocket消息代理
第六篇,Springboot中,實現更靈活的WebSocket前端
承接上文對WebSocket的介紹,由WebSocket的發送接收信息談起,對STOMP協議作大體介紹,最後,經過Springboot和JS,實際編寫一個WebSocket例子,實現廣播式消息發送。git
想要了解STOMP協議,以及如何使用Springboot搭建WebSocket服務的同窗。github
上一篇,咱們介紹了WebSocket的握手過程,並未詳細介紹信息的發送,只是提到了WebSocket發送是以幀爲單位的。而WebSocket協議上也並無規定其消息發送的詳細格式。那就意味着每一個使用WebSocket的開發者,都須要本身在服務端和客戶端定義一套規則,來傳輸信息。那麼,有沒有已經造好的輪子呢?答案確定是有的。這就是STOMP。web
STOMP是一個用於C/S之間進行異步消息傳輸的簡單文本協議, 全稱是Simple Text Oriented Messaging Protocol。spring
其實STOMP協議並非爲WS所設計的, 它實際上是消息隊列的一種協議, 和AMQP,JMS是平級的。 只不過因爲它的簡單性恰巧能夠用於定義WS的消息體格式。 目前不少服務端消息隊列都已經支持了STOMP, 好比RabbitMQ, Apache ActiveMQ等。不少語言也都有STOMP協議的客戶端解析庫,像JAVA的Gozirra,C的libstomp,Python的pyactivemq,JavaScript的stomp.js等等。bash
STOMP是一種基於幀的協議,一幀由一個命令,一組可選的Header和一個可選的Body組成。 STOMP是基於Text的,但也容許傳輸二進制數據。 它的默認編碼是UTF-8,但它的消息體也支持其餘編碼方式,好比壓縮編碼。服務器
STOMP服務端被設計爲客戶端能夠向其發送消息的一組目標地址。STOMP協議並無規定目標地址的格式,它由使用協議的應用本身來定義。 例如/topic/a,/queue/a,queue-a對於STOMP協議來講都是正確的。應用能夠本身規定不一樣的格式以此來代表不一樣格式表明的含義。好比應用本身能夠定義以/topic打頭的爲發佈訂閱模式,消息會被全部消費者客戶端收到,以/user開頭的爲點對點模式,只會被一個消費者客戶端收到。websocket
對於STOMP協議來講, 客戶端會扮演下列兩種角色的任意一種:
實際上,WebSocket結合STOMP至關於構建了一個消息分發隊列,客戶端能夠在上述兩個角色間轉換,訂閱機制保證了一個客戶端消息能夠經過服務器廣播到多個其餘客戶端,做爲生產者,又能夠經過服務器來發送點對點消息。
COMMAND
header1:value1
header2:value2Body^@
^@表示行結束符
一個STOMP幀由三部分組成:命令,Header(頭信息),Body(消息體)
來看一個實際的幀例子:
SEND
destination:/broker/roomId/1
content-length:57
{「type":"ENTER","content":"o7jD64gNifq-wq-C13Q5CRisJx5E"}
更多STOMP協議的細節,若是你們感興趣,能夠參考上述的官方網頁,有更多詳細的幀結構介紹。下面,咱們將主要介紹用Springboot和JS實現後端和前端,構建一個WebSocket的小型應用場景。
首先,生產者經過發送一條SEND命令消息到某個目的地址(destination),服務端request channel接受到這條SEND命令消息,若是目的地址是應用目的地址則轉到相應的由應用本身寫的業務方法作處理(對應圖中的SimpAnnotationMethod),再轉到broker(SimpleBroker)。若是目的地址是非應用目的地址則直接轉到broker。broker經過SEND命令消息來構建MESSAGE命令消息, 再經過response channel推送MESSAGE命令消息到全部訂閱此目的地址的消費者。 廢話很少說,下面直接上代碼。
咱們來實現一個簡單聊天室的第一步,每當有用戶加入聊天室時,該用戶向服務器發送加入聊天室的消息,服務器向當前聊天室內的全部用戶發送歡迎語。
在Spring中,STOMP消息會被路由到以Controller註解標識的類中。即咱們須要定義一個控制器類,並使用Controller註解來標識它,而後在其中實現具體的消息處理方法,咱們建立一個名爲GreetingController的類:
package com.xnpe.club.wbs.controller;
import com.xnpe.club.wbs.data.Greeting;
import com.xnpe.club.wbs.data.HelloMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;
@Controller //使用Controller註解來標識這是一個控制器類
public class GreetingController {
@MessageMapping("/hello") //使用MessageMapping註解來標識全部發送到「/hello」這個destination的消息,都會被路由到這個方法進行處理.
@SendTo("/topic/greetings") //使用SendTo註解來標識這個方法返回的結果,都會被髮送到它指定的destination,「/topic/greetings」.
//傳入的參數HelloMessage爲客戶端發送過來的消息,是自動綁定的。
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // 模擬處理延時
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); //根據傳入的信息,返回一個歡迎消息.
}
}
複製代碼
總體下來,greeting()方法的做用是,處理全部發到/hello這個destination的信息,並將處理的結果,發送到全部訂閱了/topic/greetings這個destination的客戶端。其中模擬的延時,其本質是爲了演示在WebSocket中,咱們無需考慮超時這樣的問題,即上一篇文章提到的,客戶端與服務端鏈接創建後,服務端能夠根據實際場景,在「任何有須要」的時候「推送」消息到客戶端,直到鏈接釋放。
剛纔咱們已經建立了消息處理控制器,也就是咱們的業務處理邏輯。如今咱們要爲Spring配置WebSocket和STOMP消息設置。 建立一個名爲WebSocketController的類:
package com.xnpe.club.wbs.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration //使用Configuration註解標識這是一個Springboot的配置類.
@EnableWebSocketMessageBroker //使用此註解來標識使能WebSocket的broker.即便用broker來處理消息.
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
//實現WebSocketMessageBrokerConfigurer中的此方法,配置消息代理(broker)
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic"); //啓用SimpleBroker,使得訂閱到此"topic"前綴的客戶端能夠收到greeting消息.
config.setApplicationDestinationPrefixes("/app"); //將"app"前綴綁定到MessageMapping註解指定的方法上。如"app/hello"被指定用greeting()方法來處理.
}
@Override
//用來註冊Endpoint,「/gs-guide-websocket」即爲客戶端嘗試創建鏈接的地址。
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
複製代碼
配置主要包含兩部份內容,一個是消息代理,另外一個是Endpoint,消息代理指定了客戶端訂閱地址,以及發送消息的路由地址;Endpoint指定了客戶端創建鏈接時的請求地址。
至此,服務端的配置工做就完成了,很是簡單。如今,讓咱們實現一個前端頁面,來驗證服務的工做狀況。
針對STOMP,前端咱們採用JavaScript的stomp的客戶端實現stomp.js以及WebSocket的實現SockJS。此處只展現核心代碼。
//使用SockJS和stomp.js來打開「gs-guide-websocket」地址的鏈接,這也是咱們使用Spring構建的SockJS服務。
function connect() {
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
//鏈接成功後的回調方法
setConnected(true);
console.log('Connected: ' + frame);
//訂閱/topic/greetings地址,當服務端向此地址發送消息時,客戶端便可收到。
stompClient.subscribe('/topic/greetings', function (greeting) {
//收到消息時的回調方法,展現歡迎信息。
showGreeting(JSON.parse(greeting.body).content);
});
});
}
//斷開鏈接的方法
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
//將用戶輸入的名字信息,使用STOMP客戶端發送到「/app/hello」地址。它正是咱們在GreetingController中定義的greeting()方法所處理的地址.
function sendName() {
stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
}
複製代碼
本篇例子實現代碼連接地址: SpringWebSocket Github
至此,咱們實現了一個最簡單的使用Spring,基於STOMP的WebSocket例子。下一篇咱們會基於這個例子,繼續完善聊天室功能,實現點對點的通訊功能。即兩個用戶如何點對點的聊天,敬請期待。
小銘出品,必屬精品
歡迎關注xNPE技術論壇,更多原創乾貨每日推送。