SpringBoot+Redis实现分布式WebSocket

打印 上一主题 下一主题

主题 648|帖子 648|积分 1944

什么是分布式WebSocket?

是指在分布式系统架构中实现WebSocket的通讯机制,它答应在不同的服务器节点之间共享和同步WebSocket会话状态,从而实现跨多个服务器的实时消息通报。

在分布式环境中实现WebSocket的挑衅主要包罗以下几点:

  • 会话共享:在分布式系统中,用户的WebSocket毗连大概与不同的服务器创建,这就要求系统可以或许在不同服务器间共享WebSocket会话信息,以便消息可以或许被正确地通报到所有相关的客户端。
  • 负载平衡:使用负载平衡可以进步系统的可用性和伸缩性。但是,当WebSocket哀求在服务器之间负载平衡时,需要确保客户端可以与正确的服务器创建毗连,而且可以或许接收到所有的消息。
  • 故障转移:在出现服务器故障时,系统需要可以或许将WebSocket会话无缝迁移到其他健康的服务器上,以保证服务的一连性。
  • 一致性:确保所有用户在任何时候看到的都是一致的消息状态,这对于实时通讯非常紧张。

为了办理这些挑衅,可以采取以下几种策略:

  • 使用消息署理:通过引入一个中央化的消息署理(如RabbitMQ、Redis Pub/Sub等),可以让所有的服务器都毗连到这个消息署理。当一个服务器需要发送消息时,它将消息发送到消息署理,然后由消息署理负责将消息分发到所有毗连的客户端。如许可以确保消息的一致性和可靠性。
  • 共享会话存储:使用一个共享的会话存储(如数据库或内存数据网格)来保存WebSocket会话的状态。如许,即使客户端最初毗连到的服务器发生故障,其他服务器也可以接管会话并继承处理消息。
  • 基于路由的负载平衡:使用智能负载平衡器(如Nginx、HAProxy等),它们可以根据特定的路由规则(如会话ID或用户ID)将WebSocket毗连定向到特定的服务器。
  • 服务发现:在微服务架构中,可以使用服务发现机制来动态地找到负责特定会话的服务器,并将消息路由到那里。
  • WebSocket署理:使用专门的WebSocket署理服务器,它可以在多个后端服务器之间署理WebSocket毗连,并确保消息的通报和会话的同步。
  • 应用层协议:计划应用层协议来处理分布式WebSocket的复杂性,比方通过引入心跳机制来检测毗连的健康状态,并通过预定的协议来同步会话状态。
总的来说,在实践中,大概需要结合多种策略来构建一个健壮的分布式WebSocket办理方案,以满足不同场景下的需求。别的,还需要思量安全性、性能和可扩展性等因素,以确保系统的稳定性和可靠性。


温故而知新:单点WebSocket实现

SpringBoot2.0集成WebSocket,实现后台向前端推送信息_springboot集成websocket-CSDN博客
https://zhengkai.blog.csdn.net/article/details/80275084
简单版本:在Java中使用Redis实现WebSocket

要在Java中使用Redis实现WebSocket,你需要使用一个支持WebSocket的Java Web框架,如Spring Boot,以及一个支持Redis的Java库,如Jedis。以下是一个简单的示例:

添加依赖项到你的pom.xml文件

  1. <dependencies>
  2.     <dependency>
  3.         <groupId>org.springframework.boot</groupId>
  4.         <artifactId>spring-boot-starter-websocket</artifactId>
  5.     </dependency>
  6.     <dependency>
  7.         <groupId>org.springframework.boot</groupId>
  8.         <artifactId>spring-boot-starter-data-redis</artifactId>
  9.     </dependency>
  10. </dependencies>
复制代码

创建一个WebSocket设置类

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
  4. import org.springframework.data.redis.core.RedisTemplate;
  5. import org.springframework.data.redis.listener.ChannelTopic;
  6. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
  7. import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
  8. import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
  9. import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
  10. import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
  11. //by zhengkai.blog.csdn.net
  12. @Configuration
  13. @EnableWebSocketMessageBroker
  14. public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  15.     @Override
  16.     public void registerStompEndpoints(StompEndpointRegistry registry) {
  17.         registry.addEndpoint("/websocket").withSockJS();
  18.     }
  19.     @Override
  20.     public void configureMessageBroker(org.springframework.messaging.simp.config.MessageBrokerRegistry registry) {
  21.         registry.enableSimpleBroker("/topic");
  22.         registry.setApplicationDestinationPrefixes("/app");
  23.     }
  24.     @Bean
  25.     public JedisConnectionFactory jedisConnectionFactory() {
  26.         return new JedisConnectionFactory();
  27.     }
  28.     @Bean
  29.     public RedisTemplate<String, Object> redisTemplate() {
  30.         RedisTemplate<String, Object> template = new RedisTemplate<>();
  31.         template.setConnectionFactory(jedisConnectionFactory());
  32.         return template;
  33.     }
  34.     @Bean
  35.     public MessageListenerAdapter messageListenerAdapter() {
  36.         return new MessageListenerAdapter(new RedisMessageListener());
  37.     }
  38.     @Bean
  39.     public RedisMessageListenerContainer redisMessageListenerContainer() {
  40.         RedisMessageListenerContainer container = new RedisMessageListenerContainer();
  41.         container.setConnectionFactory(jedisConnectionFactory());
  42.         container.addMessageListener(messageListenerAdapter(), topic());
  43.         return container;
  44.     }
  45.     @Bean
  46.     public ChannelTopic topic() {
  47.         return new ChannelTopic("websocket-topic");
  48.     }
  49. }
复制代码
创建一个WebSocket消息监听器

  1. import org.springframework.data.redis.connection.Message;
  2. import org.springframework.data.redis.connection.MessageListener;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. public class RedisMessageListener implements MessageListener {
  6.     @Override
  7.     public void onMessage(Message message, byte[] pattern) {
  8.         System.out.println("Received message: " + message);
  9.     }
  10. }
复制代码
发送消息到WebSocket客户端

在你的控制器中,你可以使用SimpMessagingTemplate来发送消息到WebSocket客户端:
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.messaging.simp.SimpMessagingTemplate;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. public class WebSocketController {
  7.     @Autowired
  8.     private SimpMessagingTemplate messagingTemplate;
  9.     @GetMapping("/send")
  10.     public String sendMessage() {
  11.         messagingTemplate.convertAndSend("/topic/websocket-topic", "Hello from Redis!");
  12.         return "Message sent!";
  13.     }
  14. }
复制代码
正式版本:用SpringBoot+Redis实现分布式WebSocket


 

  • 将消息(<用户id消息内容>)统一推送到一个消息队列(Redis、Kafka等)的的topic,然后每个应用节点都订阅这个topic,在接收到WebSocket消息后取出这个消息的“消息接收者的用户ID/用户名”,然后再比对自身是否存在相应用户的毗连,如果存在则推送消息,否则扬弃接收到的这个消息(这个消息接收者所在的应用节点会处理)
  • 在用户创建WebSocket毗连后,使用Redis缓存记任命户的WebSocket创建在哪个应用节点上,然后同样使用消息队列将消息推送到接收者所在的应用节点上面(实现上比方案一要复杂,但是网络流量会更低)
 
 1. 界说一个WebSocket Channel枚举类

  1. public enum WebSocketChannelEnum {
  2.     //测试使用的简易点对点聊天
  3.     CHAT("CHAT", "测试使用的简易点对点聊天", "/topic/reply");
  4.     WebSocketChannelEnum(String code, String description, String subscribeUrl) {
  5.         this.code = code;
  6.         this.description = description;
  7.         this.subscribeUrl = subscribeUrl;
  8.     }
  9.     /**
  10.      * 唯一CODE
  11.      */
  12.     private String code;
  13.     /**
  14.      * 描述
  15.      */
  16.     private String description;
  17.     /**
  18.      * WebSocket客户端订阅的URL
  19.      */
  20.     private String subscribeUrl;
  21.     public String getCode() {
  22.         return code;
  23.     }
  24.     public String getDescription() {
  25.         return description;
  26.     }
  27.     public String getSubscribeUrl() {
  28.         return subscribeUrl;
  29.     }
  30.     /**
  31.      * 通过CODE查找枚举类
  32.      */
  33.     public static WebSocketChannelEnum fromCode(String code){
  34.         if(StringUtils.isNoneBlank(code)){
  35.             for(WebSocketChannelEnum channelEnum : values()){
  36.                 if(channelEnum.code.equals(code)){
  37.                     return channelEnum;
  38.                 }
  39.             }
  40.         }
  41.         return null;
  42.     }
  43. }
复制代码
2. 设置基于Redis的消息队列

需要注意的是,在大中型正式项目中并不保举使用Redis实现的消息队列,由于颠末测试它并不是特别可靠,以是应该思量使用Kafka、rabbitMQ等专业的消息队列中央件
  1. @Configuration
  2. @ConditionalOnClass({JedisCluster.class})
  3. public class RedisConfig {
  4.     @Value("${spring.redis.timeout}")
  5.     private String timeOut;
  6.     @Value("${spring.redis.cluster.nodes}")
  7.     private String nodes;
  8.     @Value("${spring.redis.cluster.max-redirects}")
  9.     private int maxRedirects;
  10.     @Value("${spring.redis.jedis.pool.max-active}")
  11.     private int maxActive;
  12.     @Value("${spring.redis.jedis.pool.max-wait}")
  13.     private int maxWait;
  14.     @Value("${spring.redis.jedis.pool.max-idle}")
  15.     private int maxIdle;
  16.     @Value("${spring.redis.jedis.pool.min-idle}")
  17.     private int minIdle;
  18.     @Value("${spring.redis.message.topic-name}")
  19.     private String topicName;
  20.     @Bean
  21.     public JedisPoolConfig jedisPoolConfig(){
  22.         JedisPoolConfig config = new JedisPoolConfig();
  23.         config.setMaxTotal(maxActive);
  24.         config.setMaxIdle(maxIdle);
  25.         config.setMinIdle(minIdle);
  26.         config.setMaxWaitMillis(maxWait);
  27.         return config;
  28.     }
  29.     @Bean
  30.     public RedisClusterConfiguration redisClusterConfiguration(){
  31.         RedisClusterConfiguration configuration = new RedisClusterConfiguration(Arrays.asList(nodes));
  32.         configuration.setMaxRedirects(maxRedirects);
  33.         return configuration;
  34.     }
  35.     /**
  36.      * JedisConnectionFactory
  37.      */
  38.     @Bean
  39.     public JedisConnectionFactory jedisConnectionFactory(RedisClusterConfiguration configuration,JedisPoolConfig jedisPoolConfig){
  40.         return new JedisConnectionFactory(configuration,jedisPoolConfig);
  41.     }
  42.     /**
  43.      * 使用Jackson序列化对象
  44.      */
  45.     @Bean
  46.     public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer(){
  47.         Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
  48.         ObjectMapper objectMapper = new ObjectMapper();
  49.         objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  50.         objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  51.         serializer.setObjectMapper(objectMapper);
  52.         return serializer;
  53.     }
  54.     /**
  55.      * RedisTemplate
  56.      */
  57.     @Bean
  58.     public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory factory, Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer){
  59.         RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
  60.         redisTemplate.setConnectionFactory(factory);
  61.         //字符串方式序列化KEY
  62.         StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  63.         redisTemplate.setKeySerializer(stringRedisSerializer);
  64.         redisTemplate.setHashKeySerializer(stringRedisSerializer);
  65.         //JSON方式序列化VALUE
  66.         redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
  67.         redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
  68.         redisTemplate.afterPropertiesSet();
  69.         return redisTemplate;
  70.     }
  71.     /**
  72.      * 消息监听器
  73.      */
  74.     @Bean
  75.     MessageListenerAdapter messageListenerAdapter(MessageReceiver messageReceiver, Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer){
  76.         //消息接收者以及对应的默认处理方法
  77.         MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(messageReceiver, "receiveMessage");
  78.         //消息的反序列化方式
  79.         messageListenerAdapter.setSerializer(jackson2JsonRedisSerializer);
  80.         return messageListenerAdapter;
  81.     }
  82.     /**
  83.      * message listener container
  84.      */
  85.     @Bean
  86.     RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory
  87.             , MessageListenerAdapter messageListenerAdapter){
  88.         RedisMessageListenerContainer container = new RedisMessageListenerContainer();
  89.         container.setConnectionFactory(connectionFactory);
  90.         //添加消息监听器
  91.         container.addMessageListener(messageListenerAdapter, new PatternTopic(topicName));
  92.         return container;
  93.     }
  94. }
复制代码
这里使用的设置:
  1. spring:
  2.   ...
  3.   #redis
  4.   redis:
  5.       cluster:
  6.         nodes: namenode22:6379,datanode23:6379,datanode24:6379
  7.         max-redirects: 6
  8.       timeout: 300000
  9.       jedis:
  10.         pool:
  11.           max-active: 8
  12.           max-wait: 100000
  13.           max-idle: 8
  14.           min-idle: 0
  15.       #自定义的监听的TOPIC路径
  16.       message:
  17.         topic-name: topic-test
复制代码
3. 界说一个Redis消息的处理者

  1. @Component
  2. public class MessageReceiver {
  3.     private final Logger logger = LoggerFactory.getLogger(getClass());
  4.     @Autowired
  5.     private SimpMessagingTemplate messagingTemplate;
  6.     @Autowired
  7.     private SimpUserRegistry userRegistry;
  8.     /**
  9.      * 处理WebSocket消息
  10.      */
  11.     public void receiveMessage(RedisWebsocketMsg redisWebsocketMsg) {
  12.         logger.info(MessageFormat.format("Received Message: {0}", redisWebsocketMsg));
  13.         //1. 取出用户名并判断是否连接到当前应用节点的WebSocket
  14.         SimpUser simpUser = userRegistry.getUser(redisWebsocketMsg.getReceiver());
  15.         if(simpUser != null && StringUtils.isNoneBlank(simpUser.getName())){
  16.             //2. 获取WebSocket客户端的订阅地址
  17.             WebSocketChannelEnum channelEnum = WebSocketChannelEnum.fromCode(redisWebsocketMsg.getChannelCode());
  18.             if(channelEnum != null){
  19.                 //3. 给WebSocket客户端发送消息
  20.                 messagingTemplate.convertAndSendToUser(redisWebsocketMsg.getReceiver(), channelEnum.getSubscribeUrl(), redisWebsocketMsg.getContent());
  21.             }
  22.         }
  23.     }
  24. }
复制代码
4. 在Controller中发送WebSocket消息

  1. @Controller
  2. @RequestMapping(("/wsTemplate"))
  3. public class RedisMessageController {
  4.     private final Logger logger = LoggerFactory.getLogger(getClass());
  5.     @Value("${spring.redis.message.topic-name}")
  6.     private String topicName;
  7.     @Autowired
  8.     private SimpMessagingTemplate messagingTemplate;
  9.     @Autowired
  10.     private SimpUserRegistry userRegistry;
  11.     @Resource(name = "redisServiceImpl")
  12.     private RedisService redisService;
  13.     /**
  14.      * 给指定用户发送WebSocket消息
  15.      */
  16.     @PostMapping("/sendToUser")
  17.     @ResponseBody
  18.     public String chat(HttpServletRequest request) {
  19.         //消息接收者
  20.         String receiver = request.getParameter("receiver");
  21.         //消息内容
  22.         String msg = request.getParameter("msg");
  23.         HttpSession session = SpringContextUtils.getSession();
  24.         User loginUser = (User) session.getAttribute(Constants.SESSION_USER);
  25.         HelloMessage resultData = new HelloMessage(MessageFormat.format("{0} say: {1}", loginUser.getUsername(), msg));
  26.         this.sendToUser(loginUser.getUsername(), receiver, WebSocketChannelEnum.CHAT.getSubscribeUrl(), JsonUtils.toJson(resultData));
  27.         return "ok";
  28.     }
  29.     /**
  30.      * 给指定用户发送消息,并处理接收者不在线的情况
  31.      * @param sender 消息发送者
  32.      * @param receiver 消息接收者
  33.      * @param destination 目的地
  34.      * @param payload 消息正文
  35.      */
  36.     private void sendToUser(String sender, String receiver, String destination, String payload){
  37.         SimpUser simpUser = userRegistry.getUser(receiver);
  38.         //如果接收者存在,则发送消息
  39.         if(simpUser != null && StringUtils.isNoneBlank(simpUser.getName())){
  40.             messagingTemplate.convertAndSendToUser(receiver, destination, payload);
  41.         }
  42.         //如果接收者在线,则说明接收者连接了集群的其他节点,需要通知接收者连接的那个节点发送消息
  43.         else if(redisService.isSetMember(Constants.REDIS_WEBSOCKET_USER_SET, receiver)){
  44.             RedisWebsocketMsg<String> redisWebsocketMsg = new RedisWebsocketMsg<>(receiver, WebSocketChannelEnum.CHAT.getCode(), payload);
  45.             redisService.convertAndSend(topicName, redisWebsocketMsg);
  46.         }
  47.         //否则将消息存储到redis,等用户上线后主动拉取未读消息
  48.         else{
  49.             //存储消息的Redis列表名
  50.             String listKey = Constants.REDIS_UNREAD_MSG_PREFIX + receiver + ":" + destination;
  51.             logger.info(MessageFormat.format("消息接收者{0}还未建立WebSocket连接,{1}发送的消息【{2}】将被存储到Redis的【{3}】列表中", receiver, sender, payload, listKey));
  52.             //存储消息到Redis中
  53.             redisService.addToListRight(listKey, ExpireEnum.UNREAD_MSG, payload);
  54.         }
  55.     }
  56.     /**
  57.      * 拉取指定监听路径的未读的WebSocket消息
  58.      * @param destination 指定监听路径
  59.      * @return java.util.Map<java.lang.String,java.lang.Object>
  60.      */
  61.     @PostMapping("/pullUnreadMessage")
  62.     @ResponseBody
  63.     public Map<String, Object> pullUnreadMessage(String destination){
  64.         Map<String, Object> result = new HashMap<>();
  65.         try {
  66.             HttpSession session = SpringContextUtils.getSession();
  67.             //当前登录用户
  68.             User loginUser = (User) session.getAttribute(Constants.SESSION_USER);
  69.             //存储消息的Redis列表名
  70.             String listKey = Constants.REDIS_UNREAD_MSG_PREFIX + loginUser.getUsername() + ":" + destination;
  71.             //从Redis中拉取所有未读消息
  72.             List<Object> messageList = redisService.rangeList(listKey, 0, -1);
  73.             result.put("code", "200");
  74.             if(messageList !=null && messageList.size() > 0){
  75.                 //删除Redis中的这个未读消息列表
  76.                 redisService.delete(listKey);
  77.                 //将数据添加到返回集,供前台页面展示
  78.                 result.put("result", messageList);
  79.             }
  80.         }catch (Exception e){
  81.             result.put("code", "500");
  82.             result.put("msg", e.getMessage());
  83.         }
  84.         return result;
  85.     }
  86. }
复制代码
5. WebSocket相关设置

  1. @Configuration
  2. @EnableWebSocketMessageBroker
  3. public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{
  4.     @Autowired
  5.     private AuthHandshakeInterceptor authHandshakeInterceptor;
  6.     @Autowired
  7.     private MyHandshakeHandler myHandshakeHandler;
  8.     @Autowired
  9.     private MyChannelInterceptor myChannelInterceptor;
  10.     @Override
  11.     public void registerStompEndpoints(StompEndpointRegistry registry) {
  12.         registry.addEndpoint("/chat-websocket")
  13.                 .addInterceptors(authHandshakeInterceptor)
  14.                 .setHandshakeHandler(myHandshakeHandler)
  15.                 .withSockJS();
  16.     }
  17.     @Override
  18.     public void configureMessageBroker(MessageBrokerRegistry registry) {
  19.         //客户端需要把消息发送到/message/xxx地址
  20.         registry.setApplicationDestinationPrefixes("/message");
  21.         //服务端广播消息的路径前缀,客户端需要相应订阅/topic/yyy这个地址的消息
  22.         registry.enableSimpleBroker("/topic");
  23.         //给指定用户发送消息的路径前缀,默认值是/user/
  24.         registry.setUserDestinationPrefix("/user/");
  25.     }
  26.     @Override
  27.     public void configureClientInboundChannel(ChannelRegistration registration) {
  28.         registration.interceptors(myChannelInterceptor);
  29.     }
  30. }
复制代码
6. 示例页面

  1. <head>
  2.     <meta content="text/html;charset=UTF-8"/>
  3.     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  4.     <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
  5.     <meta name="viewport" content="width=device-width, initial-scale=1"/>
  6.     <title>Chat With STOMP Message</title>
  7.     <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  8.     <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
  9.     <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
  10.     <script th:src="@{/layui/layui.js}"></script>
  11.     <script th:src="@{/layui/lay/modules/layer.js}"></script>
  12.     <link th:href="@{/layui/css/layui.css}" rel="stylesheet">
  13.     <link th:href="@{/layui/css/modules/layer/default/layer.css}" rel="stylesheet">
  14.     <link th:href="@{/css/style.css}" rel="stylesheet">
  15.     <style type="text/css">
  16.         #connect-container {
  17.             margin: 0 auto;
  18.             width: 400px;
  19.         }
  20.         #connect-container div {
  21.             padding: 5px;
  22.             margin: 0 7px 10px 0;
  23.         }
  24.         .message input {
  25.             padding: 5px;
  26.             margin: 0 7px 10px 0;
  27.         }
  28.         .layui-btn {
  29.             display: inline-block;
  30.         }
  31.     </style>
  32.     <script type="text/javascript">
  33.         var stompClient = null;
  34.         $(function () {
  35.             var target = $("#target");
  36.             if (window.location.protocol === 'http:') {
  37.                 target.val('http://' + window.location.host + target.val());
  38.             } else {
  39.                 target.val('https://' + window.location.host + target.val());
  40.             }
  41.         });
  42.         function setConnected(connected) {
  43.             var connect = $("#connect");
  44.             var disconnect = $("#disconnect");
  45.             var echo = $("#echo");
  46.             if (connected) {
  47.                 connect.addClass("layui-btn-disabled");
  48.                 disconnect.removeClass("layui-btn-disabled");
  49.                 echo.removeClass("layui-btn-disabled");
  50.             } else {
  51.                 connect.removeClass("layui-btn-disabled");
  52.                 disconnect.addClass("layui-btn-disabled");
  53.                 echo.addClass("layui-btn-disabled");
  54.             }
  55.             connect.attr("disabled", connected);
  56.             disconnect.attr("disabled", !connected);
  57.             echo.attr("disabled", !connected);
  58.         }
  59.         //连接
  60.         function connect() {
  61.             var target = $("#target").val();
  62.             var ws = new SockJS(target);
  63.             stompClient = Stomp.over(ws);
  64.             stompClient.connect({}, function () {
  65.                 setConnected(true);
  66.                 log('Info: STOMP connection opened.');
  67.                 //连接成功后,主动拉取未读消息
  68.                 pullUnreadMessage("/topic/reply");
  69.                 //订阅服务端的/topic/reply地址
  70.                 stompClient.subscribe("/user/topic/reply", function (response) {
  71.                     log(JSON.parse(response.body).content);
  72.                 })
  73.             },function () {
  74.                 //断开处理
  75.                 setConnected(false);
  76.                 log('Info: STOMP connection closed.');
  77.             });
  78.         }
  79.         //断开连接
  80.         function disconnect() {
  81.             if (stompClient != null) {
  82.                 stompClient.disconnect();
  83.                 stompClient = null;
  84.             }
  85.             setConnected(false);
  86.             log('Info: STOMP connection closed.');
  87.         }
  88.         //向指定用户发送消息
  89.         function sendMessage() {
  90.             if (stompClient != null) {
  91.                 var receiver = $("#receiver").val();
  92.                 var msg = $("#message").val();
  93.                 log('Sent: ' + JSON.stringify({'receiver': receiver, 'msg':msg}));
  94.                 $.ajax({
  95.                     url: "/wsTemplate/sendToUser",
  96.                     type: "POST",
  97.                     dataType: "json",
  98.                     async: true,
  99.                     data: {
  100.                         "receiver": receiver,
  101.                         "msg": msg
  102.                     },
  103.                     success: function (data) {
  104.                     }
  105.                 });
  106.             } else {
  107.                 layer.msg('STOMP connection not established, please connect.', {
  108.                     offset: 'auto'
  109.                     ,icon: 2
  110.                 });
  111.             }
  112.         }
  113.         //从服务器拉取未读消息
  114.         function pullUnreadMessage(destination) {
  115.             $.ajax({
  116.                 url: "/wsTemplate/pullUnreadMessage",
  117.                 type: "POST",
  118.                 dataType: "json",
  119.                 async: true,
  120.                 data: {
  121.                     "destination": destination
  122.                 },
  123.                 success: function (data) {
  124.                     if (data.result != null) {
  125.                         $.each(data.result, function (i, item) {
  126.                             log(JSON.parse(item).content);
  127.                         })
  128.                     } else if (data.code !=null && data.code == "500") {
  129.                         layer.msg(data.msg, {
  130.                             offset: 'auto'
  131.                             ,icon: 2
  132.                         });
  133.                     }
  134.                 }
  135.             });
  136.         }
  137.         //日志输出
  138.         function log(message) {
  139.             console.debug(message);
  140.         }
  141.     </script>
  142. </head>
  143. <body>
  144.     <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being
  145.         enabled. Please enable
  146.         Javascript and reload this page!</h2></noscript>
  147.     <div>
  148.         <div id="connect-container" class="layui-elem-field">
  149.             <legend>Chat With STOMP Message</legend>
  150.             <div>
  151.                 <input id="target" type="text" class="layui-input" size="40" style="width: 350px" value="/chat-websocket"/>
  152.             </div>
  153.             <div>
  154.                 <button id="connect" class="layui-btn layui-btn-normal" onclick="connect();">Connect</button>
  155.                 <button id="disconnect" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"
  156.                         onclick="disconnect();">Disconnect
  157.                 </button>
  158.             </div>
  159.             <div class="message">
  160.                 <input id="receiver" type="text" class="layui-input" size="40" style="width: 350px" placeholder="接收者姓名" value=""/>
  161.                 <input id="message" type="text" class="layui-input" size="40" style="width: 350px" placeholder="消息内容" value=""/>
  162.             </div>
  163.             <div>
  164.                 <button id="echo" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"
  165.                         onclick="sendMessage();">Send Message
  166.                 </button>
  167.             </div>
  168.         </div>
  169.     </div>
  170. </body>
  171. </html>
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

半亩花草

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表