WebSocket的故事(四)—— Spingboot中,如何利用WebSocket和STOMP快速構建點對點的訊息模式(2)
最近事情太多,也有好久沒有更新了。在此感謝大家的持續關注。如果有任何問題,都可以私信我一起討論。
概述
本文是 Socket/">WebSocket的故事 系列第三篇第二節,將針對上篇的程式碼介紹,給出一個STOMP實現點對點訊息的簡單例子。WebSocket的故事系列計劃分六大篇,旨在由淺入深的介紹WebSocket以及在Springboot中如何快速構建和使用WebSocket提供的能力。
本系列計劃包含如下幾篇文章:
第二篇,Spring中如何利用STOMP快速構建WebSocket廣播式訊息模式
第三篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的訊息模式(1)
第四篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的訊息模式(2)
第五篇,Springboot中,自定義WebSocket訊息代理
第六篇,Springboot中,實現更靈活的WebSocket
本篇的主線
上一篇由 @SendTo
和 @SendToUser
開始,深入Spring的WebSocket訊息傳送關鍵程式碼進行講解。本篇將具體實現一個基於STOMP的點對點訊息示例,並針對性的進行一些說明。
在本篇編寫過程中,我也查看了一些網上的例子,多數都存在著或多或少的問題,能跑起來的很少,所以我也在文後給出了Github的示例連結,有需要的同學可以自取。
本篇適合的讀者
想要了解STOMP協議,Spring內部程式碼細節,以及如何使用Springboot搭建WebSocket服務的同學。
實現一個點對點訊息模式
一、引入 WebSecurity
實現使用者管理
講到點對點訊息,想象一下常見的如微信、QQ這些聊天工具,都是有使用者管理模組的,包括資料庫等等實現。我們這裡為了簡化,採用 WebSecurity
實現一個基於記憶體的簡單使用者登入管理,即可在服務端,儲存兩個使用者資訊,即可讓這兩個使用者互發資訊。
1. 引入依賴
<!-- 引入security模組 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> 複製程式碼
2. 實現 WebSecurityConfig
這裡我們構建兩個記憶體級別的使用者賬戶,以便我們在後面模擬互發訊息。
package com.xnpe.chat.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/","/login").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/chat") .permitAll() .and() .logout() .permitAll(); } //宣告兩個記憶體儲存使用者 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("Xiao Ming").password(new BCryptPasswordEncoder().encode("123")).roles("USER") .and().passwordEncoder(new BCryptPasswordEncoder()) .withUser("Suby").password(new BCryptPasswordEncoder().encode("123")).roles("USER"); } @Override public void configure(WebSecurity web){ web.ignoring().antMatchers("/resources/static/**"); } } 複製程式碼
二、實現 WebSocket
和頁面的配置
兩個記憶體級別的使用者賬戶建立好以後,我們來進行 WebSocket
和頁面相關的配置。
1. 配置頁面資源路由
package com.xnpe.chat.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.*; @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); registry.addViewController("/chat").setViewName("chat"); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); } } 複製程式碼
2. 配置 WebSocket STOMP
這裡我們註冊一個Endpoint名為 Chat
,並註冊一個訊息代理,名為 queue
。
package com.xnpe.chat.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{ @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/Chat").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/queue"); } } 複製程式碼
三、實現 WebSocket
的訊息處理
客戶端會將訊息傳送到 chat
這個指定的地址,它會被 handleChat
捕獲並處理。我們這裡做了個硬邏輯,如果資訊是由 Xiao Ming
發來的,我們會將它路由給 Suby
。反之亦然。
1. Controller的實現
這裡強調一下,我們監聽的Mapping地址是 chat
,所以後續在客戶端傳送訊息的時候,要注意訊息都是發到伺服器的這個地址的。服務端在接收到訊息後,會將訊息路由給 /queue/notification
這個地址,那麼也就是說,我們客戶端WebSocket訂閱的地址即為 /queue/notification
。
package com.xnpe.chat.controller; import com.xnpe.chat.data.Info; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; import java.security.Principal; @Controller public class WebSocketController { @Autowired private SimpMessagingTemplate messagingTemplate; @MessageMapping("/chat") public void handleChat(Principal principal, Info info) { if (principal.getName().equals("Xiao Ming")) { messagingTemplate.convertAndSendToUser("Suby", "/queue/notification", principal.getName() + " send message to you: " + info.getInfo()); } else { messagingTemplate.convertAndSendToUser("Xiao Ming", "/queue/notification", principal.getName() + " send message to you: " + info.getInfo()); } } } 複製程式碼
2. 訊息Bean
用來承載互發的訊息結構
package com.xnpe.chat.data; public class Info { private String info; public String getInfo() { return info; } } 複製程式碼
四、編寫客戶端Html頁面
1. 實現登入頁 login.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <meta charset="UTF-8" /> <head> <title>登陸頁面</title> </head> <body> <div th:if="${param.error}"> 無效的賬號和密碼 </div> <div th:if="${param.logout}"> 你已登出 </div> <form th:action="@{/login}" method="post"> <div><label> 賬號 : <input type="text" name="username"/> </label></div> <div><label> 密碼: <input type="password" name="password"/> </label></div> <div><input type="submit" value="登陸"/></div> </form> </body> </html> 複製程式碼
2. 實現聊天頁 chat.html
強調一下兩個要點:
- 連線WebSocket時,我們指定的是
Chat
這個Endpoint。傳送訊息時,我們要將訊息傳送到伺服器所mapping的地址上,即/chat
。 - 由於服務端會將資訊發到
/queue/notification
這個訊息代理上,所以我們訂閱的也是這個地址,因為我們要實現的是一對一的訊息(根據上一篇的內容,不理解的同學可以參考上一篇文章),這裡在訂閱時要加上user
字首。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <meta charset="UTF-8" /> <head> <title>歡迎進入聊天室</title> <script th:src="@{sockjs.min.js}"></script> <script th:src="@{stomp.min.js}"></script> <script th:src="@{jquery.js}"></script> </head> <body> <p> 聊天室 </p> <form id="chatForm"> <textarea rows="4" cols="60" name="text"></textarea> <input type="submit"/> </form> <script th:inline="javascript"> $('#chatForm').submit(function(e){ e.preventDefault(); var text = $('#chatForm').find('textarea[name="text"]').val(); sendSpittle(text); $('#chatForm').clean(); }); //連結endpoint名稱為 "/Chat" 的endpoint。 var sock = new SockJS("/Chat"); var stomp = Stomp.over(sock); stomp.connect('abc', 'abc', function(frame) { stomp.subscribe("/user/queue/notification", handleNotification); }); function handleNotification(message) { $('#output').append("<b>Received: " + message.body + "</b><br/>") } function sendSpittle(text) { stomp.send("/chat", {}, JSON.stringify({ 'info': text })); } $('#stop').click(function() {sock.close()}); </script> <div id="output"></div> </body> </html> 複製程式碼
演示點對點訊息
以上,我們程式的所有關鍵程式碼均已實現了。啟動後,訪問localhost:8080/login即可進入到登入頁。

分別開啟兩個頁面,輸入賬號和密碼(程式碼中硬編碼的兩個賬戶資訊)。即可進入到chat頁面。

在輸入框中輸入資訊,然後點選提交,訊息會被髮送到另一個使用者處。

程式碼
本篇所用的程式碼工程已上傳至Github,想要體驗的同學自取。
GitHub-STOMP實現點對點訊息
總結
本篇羅列了基於STOMP實現點對點訊息的一個基本步驟,比較簡單,注意客戶端傳送訊息的地址和訂閱的地址即可。由於採用STOMP,我們實現的點對點訊息是基於使用者地址的,即STOMP實現了使用者地址到會話session的一個對映,這也幫助我們能夠輕鬆的給對端使用者傳送訊息,而不必關心底層實現的細節。但如果我們想自己封裝更復雜的業務邏輯,管理使用者的WebSocket session,更靈活的給使用者傳送資訊,這就是我們下一篇所要講述的內容,不使用STOMP,看看如何來實現更靈活的WebSocket點對點通訊。
歡迎持續關注
小銘出品,必屬精品
歡迎關注xNPE技術論壇,更多原創乾貨每日推送。
