最近事情太多,也有很久没有更新了。在此感谢你们的持续关注。若是有任何问题,均可以私信我一块儿讨论。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技术论坛,更多原创干货每日推送。