WebSocket 的故事(四)—— Spingboot 中,如何利用 WebSocket 和 STOMP 快速構建點對點的消息模式(2)

題外話

最近事情太多,也有很久沒有更新了。在此感謝你們的持續關注。若是有任何問題,均可以私信我一塊兒討論。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

實現一個點對點消息模式

1、引入WebSecurity實現用戶管理

講到點對點消息,想象一下常見的如微信、QQ這些聊天工具,都是有用戶管理模塊的,包括數據庫等等實現。咱們這裏爲了簡化,採用WebSecurity實現一個基於內存的簡單用戶登陸管理,便可在服務端,保存兩個用戶信息,便可讓這兩個用戶互發信息。spring

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/**");
    }

}
複製代碼

2、實現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");
    }
}
複製代碼

3、實現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;
    }
}

複製代碼

4、編寫客戶端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技術論壇,更多原創乾貨每日推送。

相關文章
相關標籤/搜索