最近事情太多,也有很久沒有更新了。在此感謝你們的持續關注。若是有任何問題,均可以私信我一塊兒討論。javascript
本文是WebSocket的故事系列第三篇第二節,將針對上篇的代碼介紹,給出一個STOMP實現點對點消息的簡單例子。WebSocket的故事系列計劃分六大篇,旨在由淺入深的介紹WebSocket以及在Springboot中如何快速構建和使用WebSocket提供的能力。html
本系列計劃包含以下幾篇文章:java
第一篇,什麼是WebSocket以及它的用途
第二篇,Spring中如何利用STOMP快速構建WebSocket廣播式消息模式
第三篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的消息模式(1)
第四篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的消息模式(2)
第五篇,Springboot中,實現網頁聊天室之自定義WebSocket消息代理
第六篇,Springboot中,實現更靈活的WebSocketjquery
上一篇由@SendTo
和@SendToUser
開始,深刻Spring的WebSocket消息發送關鍵代碼進行講解。本篇將具體實現一個基於STOMP的點對點消息示例,並針對性的進行一些說明。git
在本篇編寫過程當中,我也查看了一些網上的例子,多數都存在着或多或少的問題,能跑起來的不多,因此我也在文後給出了Github的示例連接,有須要的同窗能夠自取。github
想要了解STOMP協議,Spring內部代碼細節,以及如何使用Springboot搭建WebSocket服務的同窗。web
WebSecurity
實現用戶管理講到點對點消息,想象一下常見的如微信、QQ這些聊天工具,都是有用戶管理模塊的,包括數據庫等等實現。咱們這裏爲了簡化,採用WebSecurity
實現一個基於內存的簡單用戶登陸管理,便可在服務端,保存兩個用戶信息,便可讓這兩個用戶互發信息。spring
<!-- 引入security模塊 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
複製代碼
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
和頁面相關的配置。服務器
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/");
}
}
複製代碼
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
。反之亦然。
這裏強調一下,咱們監聽的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());
}
}
}
複製代碼
用來承載互發的消息結構
package com.xnpe.chat.data;
public class Info {
private String info;
public String getInfo() {
return info;
}
}
複製代碼
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>
複製代碼
chat.html
強調一下兩個要點:
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技術論壇,更多原創乾貨每日推送。