springboot websocket集群(stomp协议)连接时候传递参数

 更新时间:2019年07月06日 08:31:24   作者:郝二驴  
这篇文章主要介绍了springboot websocket集群(stomp协议)连接时候传递参数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

最近在公司项目中接到个需求。就是后台跟前端浏览器要保持长连接,后台主动往前台推数据。

网上查了下,websocket stomp协议处理这个很简单。尤其是跟springboot 集成。

但是由于开始是单机玩的,很顺利。

但是后面部署到生产搞集群的话,就会出问题了。

假如集群两个节点,浏览器A与节点A建立连接,A节点发的消息浏览器A节点肯定能收到。但是B节点由于没有跟浏览器A建立连接。B节点发的消息浏览器就收不到了。

网上也查了好多,但是没有一个说的很清楚的,也很多都是理论层面的。

还有很多思路都是通过session获取信息的。但是这都不是我需要的。我需要的是从前台传递参数,连接的时候每个节点保存下。然后通过SimpleUserRegistry.getUser获取。

话不多说,直接上代码。

<script type="text/javascript" src="${request.contextPath}/scripts/sockjs.min.js"></script>
<script type="text/javascript" src="${request.contextPath}/scripts/stomp.min.js"></script>
var WEB_SOCKET = {
 
  topic : "",
  url : "",
  stompClient : null,
  
  connect : function(url, topic, callback,userid) {
   this.url = url;
   this.topic = topic;
   var socket = new SockJS(url); //连接SockJS的endpoint名称为"endpointOyzc"
   WEB_SOCKET.stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端
   WEB_SOCKET.stompClient.connect({userid:userid},function(frame){//连接WebSocket服务端
    // console.log('Connected:' + frame);
    //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息
    WEB_SOCKET.stompClient.subscribe(topic, callback);
   });
  }
};

这是响应的前端代码。只需要引入两个js。调用new SockJS(url) 就代表跟服务器建立连接了。

@Configuration

//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

 @Autowired
 private GetHeaderParamInterceptor getHeaderParamInterceptor;

 @Override
 //注册STOMP协议的节点(endpoint),并映射指定的url
 public void registerStompEndpoints(StompEndpointRegistry registry) {
  //注册一个STOMP的endpoint,并指定使用SockJS协议
  registry.addEndpoint("/endpointOyzc")
    .setAllowedOrigins("*")
    .withSockJS();
  /* registry.addEndpoint("/endpointOyzc")
    .setAllowedOrigins("*")
    .setHandshakeHandler(xlHandshakeHandler)
    .withSockJS();*/
 }

 @Override
 //配置消息代理(Message Broker)
 public void configureMessageBroker(MessageBrokerRegistry registry) {
  //点对点应配置一个/user消息代理,广播式应配置一个/topic消息代理
  registry.enableSimpleBroker("/topic", "/user");
  // 全局使用的消息前缀(客户端订阅路径上会体现出来)
  //registry.setApplicationDestinationPrefixes("/app");
  //点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
  registry.setUserDestinationPrefix("/user");
 }

 /**
  * 采用自定义拦截器,获取connect时候传递的参数
  *
  * @param registration
  */
 @Override
 public void configureClientInboundChannel(ChannelRegistration registration) {
  registration.interceptors(getHeaderParamInterceptor);
 }
}

注:上面的endpointOyzc就是前端的url。后面注册端点,前台链接。

然后注意下configureClientInboundChannel这个方法,这个方法里面注入拦截器就是为了链接时候接收参数的。

/**
 * @author : hao
 * @description : websocket建立链接的时候获取headeri里认证的参数拦截器。
 * @time : 2019/7/3 20:42
 */
@Component
public class GetHeaderParamInterceptor extends ChannelInterceptorAdapter {

 @Override
 public 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("userid");
    if (name instanceof LinkedList) {
     // 设置当前访问的认证用户
     accessor.setUser(new JqxxPrincipal(((LinkedList) name).get(0).toString()));
    }
   }
  }
  return message;
 }
}
/**
 * @author : hao
 * @description : 自定义的java.security.Principal
 * @time : 2019/7/3 20:42
 */
public class JqxxPrincipal implements Principal {

 private String loginName;

 public JqxxPrincipal(String loginName) {
  this.loginName = loginName;
 }

 @Override
 public String getName() {
  return loginName;
 }
}

这样就存入的前台传的参数。

后台发消息的时候怎么发呢?

/**
 * @author : hao
 * @description : websocket发送代理,负责发送消息
 * @time : 2019/7/4 11:01
 */
@Component
@Slf4j
public class WebsocketSendProxy<T> {
 @Autowired
 private SimpMessagingTemplate template;

 @Autowired
 private SimpUserRegistry userRegistry;

 @Resource(name = "redisServiceImpl")
 private RedisService redisService;

 @Value("spring.redis.message.topic-name")
 private String topicName;

 public void sendMsg(RedisWebsocketMsg<T> redisWebsocketMsg) {
  SimpUser simpUser = userRegistry.getUser(redisWebsocketMsg.getReceiver());
  log.info("发送消息前获取接收方为{},根据Registry获取本节点上这个用户{}", redisWebsocketMsg.getReceiver(), simpUser);
  if (simpUser != null && StringUtils.isNotBlank(simpUser.getName())) {
   //2. 获取WebSocket客户端的订阅地址
   WebSocketChannelEnum channelEnum = WebSocketChannelEnum.fromCode(redisWebsocketMsg.getChannelCode());
   if (channelEnum != null) {
    //3. 给WebSocket客户端发送消息
    template.convertAndSendToUser(redisWebsocketMsg.getReceiver(), channelEnum.getSubscribeUrl(), redisWebsocketMsg.getContent());
   }
  } else {
   //给其他订阅了主题的节点发消息,因为本节点没有
   redisService.convertAndSend(topicName, redisWebsocketMsg);
  }

 }
}

可以发现上面代码利用了redis监听模型,也就是redis模型的消息队列

/**
 * @author : hao
 * @description : redis消息监听实现类,接收处理类
 * @time : 2019/7/3 14:00
 */
@Component
@Slf4j
public class MessageReceiver {

 @Autowired
 private SimpMessagingTemplate messagingTemplate;

 @Autowired
 private SimpUserRegistry userRegistry;

 /**
  * 处理WebSocket消息
  */
 public void receiveMessage(RedisWebsocketMsg redisWebsocketMsg) {
  log.info(MessageFormat.format("Received Message: {0}", redisWebsocketMsg));
  //1. 取出用户名并判断是否连接到当前应用节点的WebSocket
  SimpUser simpUser = userRegistry.getUser(redisWebsocketMsg.getReceiver());

  if (simpUser != null && StringUtils.isNotBlank(simpUser.getName())) {
   //2. 获取WebSocket客户端的订阅地址
   WebSocketChannelEnum channelEnum = WebSocketChannelEnum.fromCode(redisWebsocketMsg.getChannelCode());
   if (channelEnum != null) {
    //3. 给WebSocket客户端发送消息
    messagingTemplate.convertAndSendToUser(redisWebsocketMsg.getReceiver(), channelEnum.getSubscribeUrl(), redisWebsocketMsg.getContent());
   }
  }
 }
}

redis消息模型只贴部分代码就好了

/**
  * 消息监听器
  */
 @Bean
 MessageListenerAdapter messageListenerAdapter(MessageReceiver messageReceiver, Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer){
  //消息接收者以及对应的默认处理方法
  MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(messageReceiver, "receiveMessage");
  //消息的反序列化方式
  messageListenerAdapter.setSerializer(jackson2JsonRedisSerializer);

  return messageListenerAdapter;
 }

 /**
  * message listener container
  */
 @Bean
 RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory
   , MessageListenerAdapter messageListenerAdapter){
  RedisMessageListenerContainer container = new RedisMessageListenerContainer();
  container.setConnectionFactory(connectionFactory);
  //添加消息监听器
  container.addMessageListener(messageListenerAdapter, new PatternTopic(topicName));

  return container;
 }

上面的思路大体如下:客户端简历链接时候,传过来userid保存起来。发消息的时候 通过userRegistry获取,能获取到就证明是跟本节点建立的链接,直接用本节点发消息就好了。

如果不是就利用redis消息队列,把消息推出去。每个节点去判断获取看下是不是本节点的userid。这样就实现了集群的部署。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot使用iText7实现将HTML转成PDF并添加页眉页脚水印

    SpringBoot使用iText7实现将HTML转成PDF并添加页眉页脚水印

    这篇文章主要为大家详细介绍了SpringBoot使用iText7实现将HTML转成PDF并添加页眉页脚水印的相关知识,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • Java中ReUtil正则表达式工具库的使用

    Java中ReUtil正则表达式工具库的使用

    ReUtil是Hutool库中的正则表达式工具类,提供了多种常用正则表达式操作方法,下面就来介绍一下ReUtil的使用,具有一定的参考价值,感兴趣的可以了解一下
    2025-02-02
  • Java反射技术详解

    Java反射技术详解

    这篇文章主要结合实例形式分析了介绍了Java反射技术,小编觉得这篇文章写的还不错,需要的朋友可以参考下,希望能够给你带来帮助
    2021-11-11
  • 一个注解搞定Spring Security基于Oauth2的SSO单点登录功能

    一个注解搞定Spring Security基于Oauth2的SSO单点登录功能

    本文主要介绍 同域 和 跨域 两种不同场景单点登录的实现原理,并使用 Spring Security 来实现一个最简单的跨域 SSO客户端。对Spring Security基于Oauth2的SSO单点登录功能感兴趣的朋友一起看看吧
    2021-09-09
  • Java矢量队列Vector使用示例

    Java矢量队列Vector使用示例

    Vector类实现了一个动态数组。和ArrayList很相似,但是两者是不同的Vector是同步访问的;Vector包含了许多传统的方法,这些方法不属于集合框架
    2023-01-01
  • Java中的转换流InputStreamReader解读

    Java中的转换流InputStreamReader解读

    InputStreamReader是Java.io包中的一个类,用于将字节输入流转换为字符输入流,它继承自java.io.Reader类,提供了两种构造方法,可以使用默认或指定字符集创建实例,常用方法包括读取字符、判断是否准备好读取数据和关闭流
    2024-09-09
  • Java中定时任务的6种实现方式

    Java中定时任务的6种实现方式

    这篇文章主要给大家分享的是Java中定时任务的6种实现方式,几乎在所有的项目中,定时任务的使用都是不可或缺的,如果使用不当甚至会造成资损,下面文章我们就来看看Java中定时任务的具体使用方式吧
    2021-10-10
  • Spring Security OAuth2集成短信验证码登录以及第三方登录

    Spring Security OAuth2集成短信验证码登录以及第三方登录

    这篇文章主要介绍了Spring Security OAuth2集成短信验证码登录以及第三方登录,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • Spring AOP方法内部调用不生效的解决方案

    Spring AOP方法内部调用不生效的解决方案

    最近有个需求,统计某个方法的调用次数,开始使用 Spring AOP 实现,后来发现当方法被内部调用时,切面逻辑将不会生效,所以本文就给大家介绍了Spring AOP方法内部调用不生效的解决方案,需要的朋友可以参考下
    2025-01-01
  • Java MD5消息摘要算法原理及实现代码

    Java MD5消息摘要算法原理及实现代码

    这篇文章主要介绍了Java MD5消息摘要算法原理及实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09

最新评论