SpringBoot中建立WebSocket连接(STOMP实现发送消息给指定用户)

本文主要是介绍SpringBoot中建立WebSocket连接(STOMP实现发送消息给指定用户),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文来自:https://blog.csdn.net/qq_28988969/article/details/78134114?locationNum=9&fps=1

十分感谢博主解决了我的人生大事啊!

使用STOMP实现发送消息给指定用户步骤如下:

  • 添加pom文件依赖
  • 书写客户端用户实体类
  • 书写客户端渠道拦截适配器
  • 配置websocket stomp
  • 书写控制层
  • 书写客户端

1.添加pom文件依赖

<!-- springboot websocket -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

2.书写客户端用户实体类

自定义客户端用户实体类,封装来自于客户端的信息,相当于为每一个客户端提供唯一的标识

package com.ahut.entity;import java.security.Principal;/*** * @ClassName: User* @Description: 客户端用户* @author cheng* @date 2017年9月29日 下午3:02:54*/
public final class User implements Principal {private final String name;public User(String name) {this.name = name;}@Overridepublic String getName() {return name;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.书写客户端渠道拦截适配器

利用拦截的方式,获取包含在stomp中的用户信息,并将认证的用户信息设置到当前的访问器中

package com.ahut.websocket;import java.util.LinkedList;
import java.util.Map;import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.MessageHeaderAccessor;import com.ahut.entity.User;/*** * @ClassName: UserInterceptor* @Description: 客户端渠道拦截适配器* @author cheng* @date 2017年9月29日 下午2:40:12*/
public class UserInterceptor extends ChannelInterceptorAdapter {/*** 获取包含在stomp中的用户信息*/@SuppressWarnings("rawtypes")@Overridepublic Message<?> preSend(Message<?> message, MessageChannel channel) {StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);if (StompCommand.CONNECT.equals(accessor.getCommand())) {Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);if (raw instanceof Map) {Object name = ((Map) raw).get("name");if (name instanceof LinkedList) {// 设置当前访问器的认证用户accessor.setUser(new User(((LinkedList) name).get(0).toString()));}}}return message;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

4.配置websocket stomp

package com.ahut.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
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;import com.ahut.websocket.UserInterceptor;/*** * @ClassName: WebSocketStompConfig* @Description: springboot websocket stomp配置* @author cheng* @date 2017年9月27日 下午3:45:36*/@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig extends AbstractWebSocketMessageBrokerConfigurer {/*** 注册stomp的端点*/@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {// 允许使用socketJs方式访问,访问点为webSocketServer,允许跨域// 在网页上我们就可以通过这个链接// http://localhost:8080/webSocketServer// 来和服务器的WebSocket连接registry.addEndpoint("/webSocketServer").setAllowedOrigins("*").withSockJS();}/*** 配置信息代理*/@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {// 订阅Broker名称registry.enableSimpleBroker("/queue", "/topic");// 全局使用的消息前缀(客户端订阅路径上会体现出来)registry.setApplicationDestinationPrefixes("/app");// 点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/// registry.setUserDestinationPrefix("/user/");}/*** 配置客户端入站通道拦截器*/@Overridepublic void configureClientInboundChannel(ChannelRegistration registration) {registration.setInterceptors(createUserInterceptor());}/*** * @Title: createUserInterceptor* @Description: 将客户端渠道拦截器加入spring ioc容器* @return*/@Beanpublic UserInterceptor createUserInterceptor() {return new UserInterceptor();}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

5.书写控制层

package com.ahut.action;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.user.SimpUser;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;import com.ahut.entity.ServerMessage;/*** * @ClassName: WebSocketAction* @Description: websocket控制层* @author cheng* @date 2017年9月27日 下午4:20:58*/
@Controller
public class WebSocketAction {private Logger logger = LoggerFactory.getLogger(this.getClass());//spring提供的发送消息模板@Autowiredprivate SimpMessagingTemplate messagingTemplate;@Autowiredprivate SimpUserRegistry userRegistry;@RequestMapping(value = "/templateTest")public void templateTest() {logger.info("当前在线人数:" + userRegistry.getUserCount());int i = 1;for (SimpUser user : userRegistry.getUsers()) {logger.info("用户" + i++ + "---" + user);}//发送消息给指定用户messagingTemplate.convertAndSendToUser("test", "/queue/message", new ServerMessage("服务器主动推的数据"));}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

代码分析: 
SimpUserRegistry用来获取连接的客户端信息 
userRegistry.getUsers()将返回一个用户列表

模拟发送信息给指定用户,浏览器访问

localhost:8080/templateTest
  • 1

使用test作为连接用户名,并且订阅了/user/queue/message主题的客户端就会收到服务器主动推送的消息

查看convertAndSendToUser的源码如下:

    @Overridepublic void convertAndSendToUser(String user, String destination, Object payload, Map<String, Object> headers,MessagePostProcessor postProcessor) throws MessagingException {Assert.notNull(user, "User must not be null");user = StringUtils.replace(user, "/", "%2F");super.convertAndSend(this.destinationPrefix + user + destination, payload, headers, postProcessor);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以发现messagingTemplate.convertAndSendToUser(“test”, “/queue/message”, new ServerMessage(“服务器主动推的数据”));最终发送的目的地地址为:

/user/test/queue/message
  • 1

若用户名中包含”/”,则替换成”%2F”

6.书写客户端

<!DOCTYPE html>
<html><head><title>stomp</title>
</head><body>Welcome<br/><input id="text" type="text" /><button onclick="send()">发送消息</button><button onclick="subscribe3()">订阅消息/user/queue/message</button><hr/><div id="message"></div>
</body><script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
<script type="text/javascript">// 建立连接对象(还未发起连接)var socket = new SockJS("http://localhost:8080/webSocketServer");// 获取 STOMP 子协议的客户端对象var stompClient = Stomp.over(socket);// 向服务器发起websocket连接并发送CONNECT帧stompClient.connect({name: 'test' // 携带客户端信息},function connectCallback(frame) {// 连接成功时(服务器响应 CONNECTED 帧)的回调方法setMessageInnerHTML("连接成功");},function errorCallBack(error) {// 连接失败时(服务器响应 ERROR 帧)的回调方法setMessageInnerHTML("连接失败");});//订阅消息function subscribe3() {stompClient.subscribe('/user/queue/message', function (response) {var returnData = JSON.parse(response.body);setMessageInnerHTML("/user/queue/message 你接收到的消息为:" + returnData.responseMessage);});}//将消息显示在网页上function setMessageInnerHTML(innerHTML) {document.getElementById('message').innerHTML += innerHTML + '<br/>';}</script></html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

代码分析: 
当你的客户端连接时,他们必须提供他们的用户名:

// 向服务器发起websocket连接并发送CONNECT帧
stompClient.connect({name: 'test' // 携带客户端信息},function connectCallback(frame) {// 连接成功时(服务器响应 CONNECTED 帧)的回调方法setMessageInnerHTML("连接成功");},function errorCallBack(error) {// 连接失败时(服务器响应 ERROR 帧)的回调方法setMessageInnerHTML("连接失败");}
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

用户需要先订阅/user/queue/message主题,才能收到发送给自己的消息

总结:

客户端订阅:/user/queue/message 
服务器推送指定用户:/user/客户端用户名/queue/message


这篇关于SpringBoot中建立WebSocket连接(STOMP实现发送消息给指定用户)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/605713

相关文章

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

java中新生代和老生代的关系说明

《java中新生代和老生代的关系说明》:本文主要介绍java中新生代和老生代的关系说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、内存区域划分新生代老年代二、对象生命周期与晋升流程三、新生代与老年代的协作机制1. 跨代引用处理2. 动态年龄判定3. 空间分

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

深度解析Java DTO(最新推荐)

《深度解析JavaDTO(最新推荐)》DTO(DataTransferObject)是一种用于在不同层(如Controller层、Service层)之间传输数据的对象设计模式,其核心目的是封装数据,... 目录一、什么是DTO?DTO的核心特点:二、为什么需要DTO?(对比Entity)三、实际应用场景解析

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方