RabbitMQ学习(八)——做WebSocket消息代理,集成Spring Boot实现消息实时推送

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Anumbrella/article/details/88594222

在前面的几篇文章中,我们讲解了RabbitMQ的大致使用情况,对RabbitMQ的使用有了更细致的了解,今天我们来讲解一下如何使用RabbitMQ来代理WebSocket,并结合Spring Boot实现消息实时推送。

一、WebSocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

我们知道:HTTP协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。

这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

而WebSocket 就是这样发明的,为了解决上面HTTP协议无法解决的问题。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

Web浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的HTTP连接不同,对服务器有重要的影响。

二、在RabbitMQ中启用WebSocket

通过官方的文档查询我们知道,在RabbitMQ中使用WebSocket是通过STOMP协议来实现的,如下:

websocket

因此我们需要启用插件rabbitmq_stomp,确保stomp协议可以,接着我们再启用rabbitmq_web_stomp开启websocket协议。

我们在RabbitMQ服务中启动插件,

rabbitmq-plugins enable rabbitmq_stomp
rabbitmq-plugins enable rabbitmq_web_stomp

  
  
  • 1
  • 2

然后重启RabbitMQ服务service rabbitmq-server restart

其中RabbitMQ运行在15672端口,stomp服务运行在15674端口。

更多关于STOMP上使用WebSocket的原理可以查看文档

port

三、结合Spring Boot实战

首先我们构建一个Spring Boot工程,然后在pom的依赖里面添加需要的包,这里添加如下:

	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-amqp</artifactId>
	</dependency>
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-websocket&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-reactor-netty&lt;/artifactId&gt;
&lt;/dependency&gt;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

创建名为WebSocketConfig.java的类来配置WebSocket,实现配置接口WebSocketMessageBrokerConfigurer

package net.anumbrella.rabbitmq.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;

/**

  • @author Anumbrella
    */
    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketConfig.class);

    @Autowired
    private MyChannelInterceptor inboundChannelInterceptor;

    @Autowired
    private AuthHandshakeInterceptor authHandshakeInterceptor;

    @Autowired
    private MyHandshakeHandler myHandshakeHandler;

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableStompBrokerRelay("/topic", “/queue”)
    .setRelayHost(“localhost”) // rabbitmq-host服务器地址
    .setRelayPort(61613) // rabbitmq-stomp 服务器服务端口
    .setClientLogin(“guest”) // 登陆账户
    .setClientPasscode(“guest”); // 登陆密码
    //定义一对一推送的时候前缀
    registry.setUserDestinationPrefix("/user/");
    //客户端需要把消息发送到/message/xxx地址
    registry.setApplicationDestinationPrefixes("/message");
    LOGGER.info(“init rabbitmq websocket MessageBroker complated.”);
    }

    /**

    • 连接站点配置
    • @param registry
      /
      @Override
      public void registerStompEndpoints(StompEndpointRegistry registry) {
      registry.addEndpoint("/ws").setAllowedOrigins("
      ")
      .setHandshakeHandler(myHandshakeHandler)
      .addInterceptors(authHandshakeInterceptor)
      .withSockJS();
      LOGGER.info("init rabbitmq websocket endpoint ");
      }

    /**

    • 输入通道配置
    • @param registration
      */
      @Override
      public void configureClientInboundChannel(ChannelRegistration registration) {
      registration.interceptors(inboundChannelInterceptor);
      registration.taskExecutor() // 线程信息
      .corePoolSize(400) // 核心线程池
      .maxPoolSize(800) // 最多线程池数
      .keepAliveSeconds(60); // 超过核心线程数后,空闲线程超时60秒则杀死
      }

    /**

    • 消息传输参数配置
    • @param registration
      */
      @Override
      public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
      registration.setSendTimeLimit(15 * 1000) // 超时时间
      .setSendBufferSizeLimit(512 * 1024) // 缓存空间
      .setMessageSizeLimit(128 * 1024); // 消息大小
      }
      }
  • 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
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

上面的注释讲解得也挺清楚的,这里大致说一下重点:

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").setAllowedOrigins("*")
                .setHandshakeHandler(myHandshakeHandler)
                .addInterceptors(authHandshakeInterceptor)
                .withSockJS();
        LOGGER.info("init rabbitmq websocket endpoint ");
    }

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

registerStompEndpoints(StompEndpointRegistry registry),这个方法的作用是添加一个服务端点,来接收客户端的连接。

addEndpoint("/ws")表示添加了一个/ws端点,客户端就可以通过这个端点来进行连接。

setHandshakeHandler(myHandshakeHandler),这个方法是自定义处理拦截器,在这里可以验证Socket连接的用户是否可靠。

addInterceptors(authHandshakeInterceptor),这个方法是添加一个TCP手势处理连接操作,是在WebSocket连接建立之前的操作。

withSockJS()的作用是开启SockJS支持。

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableStompBrokerRelay("/topic", "/queue")
                .setRelayHost("localhost")      // rabbitmq-host服务器地址
                .setRelayPort(61613)     // rabbitmq-stomp 服务器服务端口
                .setClientLogin("guest")   // 登陆账户
                .setClientPasscode("guest");  // 登陆密码
        //定义一对一推送的时候前缀
        registry.setUserDestinationPrefix("/user/");
        //客户端需要把消息发送到/message/xxx地址
        registry.setApplicationDestinationPrefixes("/message");
        LOGGER.info("init rabbitmq websocket MessageBroker complated.");
    }

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

configureMessageBroker(MessageBrokerRegistry config),这个方法的作用是定义消息代理,通俗一点讲就是设置消息连接请求的各种规范信息。

enableSimpleBroker("/topic"),表示客户端订阅地址的前缀信息,也就是客户端接收服务端消息的地址的前缀信息。

setUserDestinationPrefix("/user/"),表示指定一对一发送队列的前缀。

registry.setApplicationDestinationPrefixes("/message")指服务端接收地址的前缀,意思就是说客户端给服务端发消息的地址的前缀。

自定义AuthHandshakeInterceptor类,是在WebSocket连接建立之前的操作。如下:

package net.anumbrella.rabbitmq.config;

import net.anumbrella.rabbitmq.util.SpringContextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.HttpSession;
import java.util.Map;

/**

  • @author Anumbrella
    */
    @Component
    public class AuthHandshakeInterceptor implements HandshakeInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(AuthHandshakeInterceptor.class);

    @Override
    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {

     LOGGER.info("===============before handshake=============");
     // 在beforeHandshake中可以获取socket连接URL中的参数
    
     // 在这里可以获取session,做用户登录判断依据,这里只做了简单处理
    
     // HttpSession session = SpringContextUtils.getSession();
     // session.getAttribute("session_key") 判断具体的session存在
     // 比如,只有登录后,才可以进行websocket连接
    
     ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) serverHttpRequest;
     String user = serverRequest.getServletRequest().getParameter("user");
     if (user != null) {
         return true;
     }
     return false;
    

    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
    LOGGER.info("===after handshake=");
    }
    }

  • 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

新建我们频道相关处理类,MyChannelInterceptor。如下:

package net.anumbrella.rabbitmq.config;

import net.anumbrella.rabbitmq.entity.MyPrincipal;
import net.anumbrella.rabbitmq.util.SocketSessionRegistry;
import org.springframework.beans.factory.annotation.Autowired;
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.ChannelInterceptor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;

import java.util.LinkedList;
import java.util.Map;

/**

  • @author Anumbrella
    */
    @Component
    public class MyChannelInterceptor extends ChannelInterceptorAdapter {

    @Autowired
    private SocketSessionRegistry webAgentSessionRegistry;

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
    StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
    if (StompCommand.CONNECT.equals(accessor.getCommand())) {
    System.out.println(“连接success”);
    Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
    if (raw instanceof Map) {
    Object name = ((Map) raw).get(“name”);
    if (name instanceof LinkedList) {
    String id = ((LinkedList) name).get(0).toString();

                 //设置当前访问器的认证用户
                 accessor.setUser(new MyPrincipal(id));
                 String sessionId = accessor.getSessionId();
                 // 统计用户在线数,可通过redis来实现更好
                 webAgentSessionRegistry.registerSessionId(id, sessionId);
             }
         }
     } else if (StompCommand.DISCONNECT.equals(accessor.getCommand())) {
         //点击断开连接,这里会执行两次,第二次执行的时候,message.getHeaders.size()=5,第一次是6。直接关闭浏览器,只会执行一次,size是5。
         System.out.println("断开连接");
         MyPrincipal principal = (MyPrincipal) message.getHeaders().get(SimpMessageHeaderAccessor.USER_HEADER);
         //  如果同时发生两个连接,只有都断开才能叫做不在线
         if (message.getHeaders().size() == 5 &amp;&amp; principal.getName() != null) {
             String sessionId = accessor.getSessionId();
             webAgentSessionRegistry.unregisterSessionId(principal.getName(), sessionId);
         }
     }
     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
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

这里我只是做了一个简单WebSocket连接的简单处理,如何在连接中包含name参数,就将其赋值为认证标识,这里的MyPrincipal,做了重新覆写,以用户名为准。然后再做其他的操作。

MyPrincipal,

package net.anumbrella.rabbitmq.entity;

import java.security.Principal;

/**

  • @author Anumbrella
    */
    public class MyPrincipal implements Principal {

    private String loginName;

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

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

同时新建SocketSessionRegistry类,在里面对用户相关统计操作。

package net.anumbrella.rabbitmq.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**

  • @author Anumbrella
    */
    @Component
    @Scope(“singleton”)
    public class SocketSessionRegistry {

    private static final Logger LOGGER = LoggerFactory.getLogger(SocketSessionRegistry.class);

    /**

    • 这个集合存储session
      */
      private final ConcurrentMap<String, Set<String>> userSessionIds = new ConcurrentHashMap();

    private final Object lock = new Object();

    public SocketSessionRegistry() {
    }

    /**

    • 获取sessionId
    • @param userId
    • @return
      */
      public Set<String> getSessionIds(String userId) {
      Set set = (Set) this.userSessionIds.get(userId);
      return set != null ? set : Collections.emptySet();
      }

    /**

    • 获取所有session
    • @return
      */
      public ConcurrentMap<String, Set<String>> getAllSessionIds() {
      return this.userSessionIds;
      }

    /**

    • register session

    • @param userId

    • @param sessionId
      */
      public void registerSessionId(String userId, String sessionId) {
      Assert.notNull(userId, “User ID must not be null”);
      Assert.notNull(sessionId, “Session ID must not be null”);
      synchronized (this.lock) {
      Object set = (Set) this.userSessionIds.get(userId);
      if (set == null) {
      set = new CopyOnWriteArraySet();
      this.userSessionIds.put(userId, (Set<String>) set);
      }

       ((Set) set).add(sessionId);
      

      }
      LOGGER.info("===当前在线人数=: " + userSessionIds.size());
      }

    /**

    • remove session
    • @param userId
    • @param sessionId
      */
      public void unregisterSessionId(String userId, String sessionId) {
      Assert.notNull(userId, “User ID must not be null”);
      Assert.notNull(sessionId, “Session ID must not be null”);
      synchronized (this.lock) {
      Set set = (Set) this.userSessionIds.get(userId);
      if (set != null && set.remove(sessionId) && set.isEmpty()) {
      this.userSessionIds.remove(userId);
      }
      }
      LOGGER.info("===当前在线人数=: " + userSessionIds.size());
      }
      }
  • 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
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

然后我们添加两个controller类,SendController,ReceiveController类,模拟发送和接收的测试。在这里会使用SimpMessagingTemplate

使用org.springframework.messaging.simp.SimpMessagingTemplate类可以在服务端的任意地方给客户端发送消息。此外,在我们配置Spring支持STOMP后SimpMessagingTemplate类就会被自动装配到Spring的上下文中,因此我们只需要在想要使用的地方使用@Autowired注解注入SimpMessagingTemplate即可使用。

需要说明的是,SimpMessagingTemplate类有两个重要的方法,它们分别是:

  • public void convertAndSend(D destination, Object payload):给监听了路径destination的所有客户端发送消息payload
  • public void convertAndSendToUser(String user, String destination, Object payload):给监听了路径destination的用户user发送消息payload

SendController类,


package net.anumbrella.rabbitmq.controller;

import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**

  • @author Anumbrella
    */
    @RestController
    @RequestMapping("/websocket")
    public class SendController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    /**

    • 通知消息
      */
      @GetMapping("/notice")
      public void notice() {
      messagingTemplate.convertAndSend("/topic/notice", JSON.toJSONString(“这是通知消息!!”));
      }

    /**

    • 具体用户消息
      */
      @GetMapping("/user/{name}")
      public void user(@PathVariable(“name”) String name) {
      messagingTemplate.convertAndSendToUser(name, “/topic/reply”, JSON.toJSONString(“这是发送给” + 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
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

ReceiveController类,

package net.anumbrella.rabbitmq.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;

/**

  • @author Anumbrella
    */
    @Controller
    public class ReceiveController {

    private static final Logger LOGGER = LoggerFactory.getLogger(ReceiveController.class);

    @MessageMapping("/client")
    public void all(String message) {
    LOGGER.info("*** 来自客户端的消息 ***:" + message);
    }
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

到此我们后端代码基本完成,关于前端使用Socket,RabbitMQ推荐使用stomp-websocket

这里我结合一个React的demo来进行讲解一下,核心代码如下:

        let client = Stomp.over(new SockJS('http://localhost:8080/ws?user=211'));
        client.heartbeat.outgoing = 0;
        client.heartbeat.incoming = 0;
    this.setState(() =&gt; ({
        wsClient: client
    }));

    client.connect({
        name: '211',
    }, (frame) =&gt; {
        this.setState(() =&gt; ({
            connected: true
        }));

         client.subscribe('/user/topic/reply', (msg) =&gt; {
            const messages = this.state.pMessages;
            msg.id = Date.now();
            const newMessages = messages.concat([msg]);
            this.setState(() =&gt; ({
                pMessages: newMessages
            }));
        });

        client.subscribe('/topic/notice', (msg) =&gt; {
            const messages = this.state.pMessages;
            msg.id = Date.now();
            const newMessages = messages.concat([msg]);
            console.log("notice");
            console.log(msg);

            this.setState(() =&gt; ({
                pMessages: newMessages
            }));
        });
    });
}
  • 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

首先我们使用Stompjs来连接服务端,配置的端点为/ws,至于后面的参数是我在后台加入的判断,如果没参数是无法认证成功,因此用户可以在这里做一下session判断认证,用户是否登录成功等。

    ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) serverHttpRequest;
    String user = serverRequest.getServletRequest().getParameter("user");
    if (user != null) {
        return true;
    }
    return false;

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

连接成功后,在前端控制台我们可以看到相关输入信息。

websocket info

在后台我们也可以看到相关连接信息。
info

然后我们在浏览器中输入http://localhost:8080/websocket/notice,可以发现我们接收到相关信息。

message

我们在发送一对一信息,http://localhost:8080/websocket/user/211,我们就可以看到接收到具体用户信息。

message2

而当我们输入http://localhost:8080/websocket/user/212http://localhost:8080/websocket/user/213,是没有消息的,因为我们是没有相关用户监听,必须配置定点用户。

当我们点击前端send发送消息,后端接收到相关信息。

message3

到此,我们简单的基于RabbitMQ做WebSocket消息代理,集成Spring Boot实现消息实时推送就完成了。

代码实例:rabbitmq-websocket

参考

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值