淺析如何使用WebSocket、SockJS、STOMP實現消息實時通訊功能:websocket/SocketJS/Stomp是什么及三者的關系、stomp協議格式、如何開啟stomp、如何處理客服端發送的stomp、如何發消息給客服端、如何在任何地方發消息、如何給目標或指定用戶發消息


一、WebSocket

1、http:http超文本傳輸協議,http有1.0、1.1、 2.0幾個版本,從http1.1起,默認都開啟了Keep-Alive,保持連接持續性,簡單地說,當一個網頁打開完成后,客戶端和服務器之間用於傳輸http數據的TCP連接不會關閉,如果客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經建立的連接,這樣就降低了資源的消耗優化性能,但是Keep-Alive也是有時間限制的,還有一個客戶端只能主動發起請求才能獲取返回數據,並不能主動接收后台推送的數據,websocket便應運而生。

2、websocket:是 html5 新增加特性之一,目的是瀏覽器與服務端建立全雙工的通信方式,解決 http 請求-響應帶來過多的資源消耗,同時對特殊場景應用提供了全新的實現方式,比如聊天、股票交易、游戲等對對實時性要求較高的行業領域。

  http與websocket都是基於TCP(傳輸控制協議)的,websocket可以看做是對http協議的一個補充。

  WebSocket協議提供了通過一個套接字實現全雙工通信的功能。除了其他的功能之外,它能夠實現Web瀏覽器和服務器之間的異步通信。全雙工意味着服務器可以發送消息給瀏覽器,瀏覽器也可以發送消息給服務器。

3、SockJs:SockJS是一個JavaScript庫,為了應對許多瀏覽器不支持WebSocket協議的問題,設計了備選SockJs。SockJS 是 WebSocket 技術的一種模擬。SockJS會盡可能對應 WebSocket API,但如果WebSocket 技術不可用的話,會自動降為輪詢的方式

  SockJS會優先選擇WebSocket進行連接,但是當服務器或客戶端不支持WebSocket時,會自動在 XHR流、XDR流、iFrame事件源、iFrame HTML文件、XHR輪詢、XDR輪詢、iFrame XHR輪詢、JSONP輪詢 這幾個方案中擇優進行連接。

4、Stompjs:STOMP—— Simple Text Oriented Message Protocol——面向消息的簡單文本協議。SockJS 為 WebSocket 提供了 備選方案。但無論哪種場景,對於實際應用來說,這種通信形式層級過低。

  STOMP協議,來為瀏覽器 和 server 間的 通信增加適當的消息語義

5、WebSocket、SockJs、STOMP三者關系

  簡而言之,WebSocket 是底層協議,SockJS 是WebSocket 的備選方案,也是底層協議,而 STOMP 是基於 WebSocket(SockJS)的上層協議。

(1)HTTP協議解決了 web 瀏覽器發起請求以及 web 服務器響應請求的細節,假設 HTTP 協議 並不存在,只能使用 TCP 套接字來 編寫 web 應用,那將是一件非常痛苦的事情。

(2)直接使用 WebSocket(SockJS) 就很類似於 使用 TCP 套接字來編寫 web 應用,因為沒有高層協議,就需要我們定義應用間所發送消息的語義,還需要確保連接的兩端都能遵循這些語義;

(3)同HTTP在TCP 套接字上添加請求-響應模型層一樣,STOMP在WebSocket 之上提供了一個基於幀的線路格式層,用來定義消息語義;

二、SockJS

1、概述

  WebSocket是一個相對比較新的規范,在Web瀏覽器和應用服務器上沒有得到一致的支持。所以我們需要一種WebSocket的備選方案,而這恰恰是SockJS所擅長的。

  SockJS是WebSocket技術的一種模擬,在表面上,它盡可能對應WebSocket API,但是在底層非常智能。如果WebSocket技術不可用的話,就會選擇另外的通信方式。

2、使用SockJS

  WebSocketConfig.java

@Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(marcoHandler(), "/marco").withSockJS(); }

  只需加上withSockJS()方法就能聲明我們想要使用SockJS功能,如果WebSocket不可用的話,SockJS的備用方案就會發揮作用。

  同時需要注意的是,如果我們不是使用 SockJS,那么后端代碼就只需要把這個 withSockJS() 去掉即可,那么就會采用 websocket 來連接

  JavaScript客戶端代碼

  要在客戶端使用SockJS,需要確保加載了SockJS客戶端庫。

<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>

  除了加載SockJS客戶端庫外,要使用SockJS只需要修改兩行代碼即可:

var url = 'marco'; var sock = new SockJS(url);
//SockJS所處理的URL是http://或https://,不再是ws://和wss://

  運行效果一樣,但是客戶端–服務器之間通信的方式卻有了很大的變化。

三、使用STOMP消息

1、概述

  STOMP在WebSocket之上提供了一個基於幀的線路格式層,用來定義消息的語義。

  STOMP幀由命令、一個或多個頭信息以及負載所組成。例如如下就是發送數據的一個STOMP幀:

>>> SEND destination:/app/marco content-length:20 {"message":"Maeco!"}

  在這個簡單的樣例中,STOMP命令是SEND,表明會發送一些內容。緊接着是兩個頭信息:一個用來表示消息要發送到哪里的目的地,另外一個則包含了負載的大小。然后,緊接着是一個空行,STOMP幀的最后是負載內容

  STOMP幀中最有意思的是destination頭信息了。它表明STOMP是一個消息協議。消息會發布到某個目的地,這個目的地實際上可能真的有消息代理作為支撐。另一方面,消息處理器也可以監聽這些目的地,接收所發送過來的消息。

四、啟用STOMP消息功能

  WebSocketStompConfig.java

@Configuration @EnableWebSocketMessageBroker public class WebSocketStompConfig extends AbstractWebSocketMessageBrokerConfigurer{ @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/marcopolo").withSockJS();//為/marcopolo路徑啟用SockJS功能
 } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { //表明在topic、queue、users這三個域上可以向客戶端發消息。
        registry.enableSimpleBroker("/topic","/queue","/users"); //客戶端向服務端發起請求時,需要以/app為前綴。
        registry.setApplicationDestinationPrefixes("/app"); //給指定用戶發送一對一的消息前綴是/users/。
        registry.setUserDestinationPrefix("/users/"); } }
@Override protected Class<?>[] getServletConfigClasses() {   return new Class<?>[] {WebSocketStompConfig.class,WebConfig.class}; }

  WebSocketStompConfig 重載了registerStompEndpoints() 方法,將/marcopolo注冊為STOMP端點。

  這個路徑與之前接收和發送消息的目的地路徑有所不同。這是一個端點,客戶端在訂閱或發布消息到目的地前,要連接該端點。

  WebSocketStompConfig還通過重載configureMessageBroker() 方法配置了一個簡單的消息代理。這個方法是可選的,如果不重載它的話,將會自動配置一個簡單的內存消息代理,用它來處理以“/topic”為前綴的消息。

五、處理來自客戶端的STOMP消息

  testConroller.java

@Controller public class testConroller { @MessageMapping("/marco") public void handleShout(Shout incoming) { System.out.println("Received message:"+incoming.getMessage()); } @SubscribeMapping("/subscribe") public Shout handleSubscribe() { Shout outing = new Shout(); outing.setMessage("subscribes"); return outing; } }

  @MessageMapping注解,表明handleShout()方法能夠處理指定目的地上到達的消息。本例中目的地也就是“/app/marco”。(“/app”前綴是隱含 的,因為我們將其配置為應用的目的地前綴)

  @SubscribeMapping注解,與@MessageMapping注解相似,當收到了STOMP訂閱消息的時候,帶有@SubscribeMapping注解的方法將會被觸發。

  客戶端JavaScript代碼

<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.js"></script>
<script>
var url = 'http://'+window.location.host+'/yds/marcopolo'; var sock = new SockJS(url);  //創建SockJS連接。
var stomp = Stomp.over(sock);//創建STOMP客戶端實例。實際上封裝了SockJS,這樣就能在WebSocket連接上發送STOMP消息。
var payload = JSON.stringify({'message':'Marco!'}); stomp.connect('guest','guest',function(frame){   stomp.send("/app/marco",{},payload);   stomp.subscribe('/app/subscribe', function(message){
  }); });
</script>

六、發送消息到客戶端

  如果你想要在接收消息的時候,同時在響應中發送一條消息,那么需要做的僅僅是將內容返回就可以了。

@MessageMapping("/marco") public Shout handleShout(Shout incoming) { System.out.println("Received message:"+incoming.getMessage()); Shout outing = new Shout(); outing.setMessage("Polo"); return outing; }

  當@MessageMapping注解標示的方法有返回值的時候,返回的對象將會進行轉換(通過消息轉換器)並放到STOMP幀的負載中,然后發給消息代理。

  默認情況下,幀所發往的目的地會與觸發處理器方法的目的地相同,只不過會加上“/topic”前綴。

stomp.subscribe('/topic/marco', function(message){    // 訂閱后將會接收到消息。
});

  不過我們可以通過為方法添加@SendTo注解,重載目的地:

@MessageMapping("/marco") @SendTo("/queue/marco") public Shout handleShout(Shout incoming) { System.out.println("Received message:"+incoming.getMessage()); Shout outing = new Shout(); outing.setMessage("Polo"); return outing; } stomp.subscribe('/queue/marco', function(message){ });

七、在應用的任意地方發送消息

  Spring的SimpMessagingTemplate能夠在應用的任何地方發送消息,甚至不必以首先接收一條消息作為前提。使用SimpMessagingTemplate的最簡單方式是將它(或者其接口SimpMessageSendingOperations)自動裝配到所需的對象中。

@Autowired private SimpMessageSendingOperations simpMessageSendingOperations; @RequestMapping("/test") public void sendMessage() { simpMessageSendingOperations.convertAndSend("/topic/test", "測試SimpMessageSendingOperations "); }

八、為目標用戶發送消息

  使用@SendToUser注解,表明它的返回值要以消息的形式發送給某個認證用戶的客戶端。

    @MessageMapping("/message") @SendToUser("/topic/sendtouser") public Shout message() { Shout outing = new Shout(); outing.setMessage("SendToUser"); return outing; }
stomp.subscribe('/users/topic/sendtouser', function(message){//給指定用戶發送一對一的消息前綴是/users/。
});

  這個目的地使用了/users作為前綴,以/users作為前綴的目的地將會以特殊的方式進行處理。以/users為前綴的消息將會通過UserDestinationMessageHandler進行處理。

  UserDestinationMessageHandler的主要任務是將用戶消息重新路由到某個用戶獨有的目的地上。在處理訂閱的時候,它會將目標地址中的/users前綴去掉,並基於用戶的會話添加一個后綴。

九、為指定用戶發送消息

  SimpMessagingTemplate還提供了convertAndSendToUser()方法。convertAndSendToUser()方法能夠讓我們給特定用戶發送消息。

simpMessageSendingOperations.convertAndSendToUser("1", "/message", "測試convertAndSendToUser");
stomp.subscribe('/users/1/message', function(message){ });

  客戶端接收一對一消息的主題是"/users/"+usersId+"/message",這里的用戶Id可以是一個普通字符串,只要每個客戶端都使用自己的Id並且服務器端知道每個用戶的Id就行了。

  具體代碼實現可以看這篇博客,還比較清晰:springboot+websocket+sockjs進行消息推送【基於STOMP協議】


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM