題外話
最近事情太多,也有好久沒有更新了。在此感謝大家的持續關注。如果有任何問題,都可以私信我一起討論。
概述
本文是WebSocket的故事系列第三篇第二節,將針對上篇的程式碼介紹,給出一個STOMP實現點對點訊息的簡單例子。WebSocket的故事系列計劃分六大篇,旨在由淺入深的介紹WebSocket以及在Springboot中如何快速構建和使用WebSocket提供的能力。
本系列計劃包含如下幾篇文章:
第一篇,什麼是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,想要體驗的同學自取。
總結
本篇羅列了基於STOMP實現點對點訊息的一個基本步驟,比較簡單,注意客戶端傳送訊息的地址和訂閱的地址即可。由於採用STOMP,我們實現的點對點訊息是基於使用者地址的,即STOMP實現了使用者地址到會話session的一個對映,這也幫助我們能夠輕鬆的給對端使用者傳送訊息,而不必關心底層實現的細節。但如果我們想自己封裝更復雜的業務邏輯,管理使用者的WebSocket session,更靈活的給使用者傳送資訊,這就是我們下一篇所要講述的內容,不使用STOMP,看看如何來實現更靈活的WebSocket點對點通訊。
歡迎持續關注
小銘出品,必屬精品
歡迎關注xNPE技術論壇,更多原創乾貨每日推送。