Springboot+websocket 单点推送

之前一篇文章通过demo简单介绍了下Springboot 集成 websocket发送消息;因为工作需要,所以深入了解了下具体的使用方法;主要详情讲一对一的消息发送;

1.依赖环境配置

前端是使用的angualr,需要引入sockjs-client和webstomp-client这两个库;具体package.json中配置,然后通过yarn install安装即可;如下所示

后端使用使用Springboot,需要引入spring对websocket的支持

compile "org.springframework.boot:spring-boot-starter-websocket"

引入WebsocketConfiguration配置类,配置websocket的信息;代码如下所示

@Configuration
@EnableWebSocketMessageBroker
public class WebsocketConfiguration implements WebSocketMessageBrokerConfigurer {

    public static final String IP_ADDRESS = "IP_ADDRESS";

    private final JHipsterProperties jHipsterProperties;

    public WebsocketConfiguration(JHipsterProperties jHipsterProperties) {
        this.jHipsterProperties = jHipsterProperties;
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic", "/queue");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        String[] allowedOrigins = Optional.ofNullable(jHipsterProperties.getCors().getAllowedOrigins()).map(origins -> origins.toArray(new String[0])).orElse(new String[0]);
        registry.addEndpoint("/websocket/tracker")
            .setHandshakeHandler(defaultHandshakeHandler())
            .setAllowedOrigins(allowedOrigins)
            .withSockJS()
            .setInterceptors(httpSessionHandshakeInterceptor());
    }

    @Bean
    public HandshakeInterceptor httpSessionHandshakeInterceptor() {
        return new HandshakeInterceptor() {

            @Override
            public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
                if (request instanceof ServletServerHttpRequest) {
                    ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
                    attributes.put(IP_ADDRESS, servletRequest.getRemoteAddress());
                }
                return true;
            }

            @Override
            public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {

            }
        };
    }

    private DefaultHandshakeHandler defaultHandshakeHandler() {
        return new DefaultHandshakeHandler() {
            @Override
            protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
                Principal principal = request.getPrincipal();
                if (principal == null) {
                    Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
                    authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS));
                    principal = new AnonymousAuthenticationToken("WebsocketConfiguration", "aaaa", authorities);
                }
                return principal;
            }
        };
    }

1>config.enableSimpleBroker("/topic", "/queue");配置客户端监听的地址;topic一般是广播类消息,queue是点对点的消息;

2>registry.addEndpoint("/websocket/tracker");客户端websocket建立连接的地址;

3>setHandshakeHandler(defaultHandshakeHandler());设置默认的握手处理器;设置用户和消息的关联;

4>setInterceptors(httpSessionHandshakeInterceptor());设置socket的过滤器;可以来做权限校验,校验连接的安全性,比如登录之后可以连接,返回true,未登录禁止连接,返回false;

5>defaultHandshakeHandler();重写了determineUser方法,建立链接的时候识别用户名和session之间的对应关系,以便做到点对点的消息通知;这里我默认如果principal没有的话,用户是“aaaa”;

2.前端建立连接

const loc = this.$window.nativeWindow.location;
        let url;
        url = '//' + loc.host + loc.pathname + 'websocket/tracker';
        const socket = new SockJS(url);
        this.stompClient = Stomp.over(socket);
        const headers = {};
        headers['X-XSRF-TOKEN'] = this.csrfService.getCSRF('XSRF-TOKEN');
        this.stompClient.connect(
            headers,
            () => {
                this.connectedPromise('success');
                this.connectedPromise = null;
                console.log('connect success!!');
            }
        );

订阅点对点消息

this.connection.then(() => {
            this.subscriber = this.stompClient.subscribe('/user/queue/chat', data => {
                this.listenerObserver.next(data);
            });
        });

3.后端发送消息

@RestController
public class WebsocketController {
    private final SimpMessageSendingOperations messagingTemplate;

    public WebsocketController(SimpMessageSendingOperations messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    @GetMapping("/web-socket/test")
    public ResponseEntity<Void> test(Principal principal) {
        this.messagingTemplate.convertAndSendToUser("aaaa", "/queue/chat", "ffffffff");
        return new ResponseEntity<>(HttpStatus.OK);
    }
}

调用/web-socket/test时,发送消息到前端;这里也可以使用@MessageMapping来通过websocket前端发送消息到后端;

调用convertAndSendToUser发送消息给指定用户;这里有人可能会有疑问了,前端订阅的路径时/user/queue/chat,后端发送的消息路径是/aaaa/queue/chat,这两个路径怎么不一致?

4.源码分析

1>调用convertAndSendToUser方法,最终调用栈到UserDestinationMessageHandler的handleMessage方法中;如下所示

继续往下跟踪到DefaultUserDestinationResolver的resolveDestination方法;代码如下所示

在parse(message)中继续调用到parseMessage,如下所示

继续往下跟踪到

通过user获取对应的session;这里可以看到用户名是aaaa,对应的路径是subscription:sub-1555394040552-443;我们看前端的日志

这里是一致的,也就是说,每个订阅id,会跟用户绑定起来;

获取到用户的sessionId之后,调用getTargetDestination方法来获取用户的目标地址;

这个是最终的路径;

所以前面发送的地址/user/aaaa/queue/chat 到这里转换成了 user/queue/chat-user4lzrnqaz;用户信息被转换成了对应的sessionId;这样就跟一个唯一的前端订阅地址联系起来了;

5.不需要经过握手的点对点消息推送

这种情况也可以实现点对点的消息推送;需要做一些修改;

1>websocketConfiguation不需要设置握手处理器

后端消息发送不变,如图所示

前端订阅消息需要修改,如下

用户aaaa变成路径的一部分了,之前是/user/queue/chat即可;然后是websocketConfiguartion中,后面是“/user“,上一种方式是queue;

跟踪源码下来发现,这种方式区别在于这里

这里的user是空的,最终的sessionIds也是空的,所以后面的路径就是/user/aaaa/queue/chat;跟前端的路径是一致的;

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值