SpringBoot-WebSocket-STMOP简介与使用

  1. 云栖社区>
  2. 博客>
  3. 正文

SpringBoot-WebSocket-STMOP简介与使用

meijm0103 2018-09-06 00:40:20 浏览2625 评论0

摘要: 简介 WebSocket:是一种网络通信协议,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息详情 sockjs-client:js库,如果浏览器不支持 WebSocket,该库可以模拟对 WebSocket 的支持github STOMP:简单(流)文本定向消息协议 介绍 stomp-websocket:js库,提供一个基于STOMP客户端的WebSocket gihub CODE 已下代码在demo中都有,但是有的为了博客效果没有简化。

简介

  • WebSocket:是一种网络通信协议,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息详情
  • sockjs-client:js库,如果浏览器不支持 WebSocket,该库可以模拟对 WebSocket 的支持github
  • STOMP:简单(流)文本定向消息协议 介绍
  • stomp-websocket:js库,提供一个基于STOMP客户端的WebSocket gihub

CODE

已下代码在demo中都有,但是有的为了博客效果没有简化。

Maven 增加依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-websocket</artifactId>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

SpringBoot 配置

注意事项

  • 重写DefaultHandshakeHandler的determineUser方法来自己实现生成用户频道名称,如使用的是spring Security则可忽略此条
  • enableSimpleBroker:设置客户端接收消息的前缀
  • setUserDestinationPrefix:指定用户频道的前缀,这个前缀必须在enableSimpleBroker中设置过
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        //设置端点,前端通过 /ContextPath/端点 进行连接
        stompEndpointRegistry.addEndpoint("/any-socket").addInterceptors(new HandshakeInterceptor() {

            /**
             * 握手前拦截,往attributes存储用户信息,后续用户频道使用
             * @param request
             * @param response
             * @param wsHandler
             * @param attributes
             * @return
             * @throws Exception
             */
            @Override
            public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
                boolean result = false;
                HttpSession session =getSession(request);
                if (session != null){
                    User user =(User)getSession(request).getAttribute("user");
                    if(user != null){
                        attributes.put("user",user);
                        result = true;
                    }
                }
                return result;
            }
            @Nullable
            private HttpSession getSession(ServerHttpRequest request) {
                if (request instanceof ServletServerHttpRequest) {
                    ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
                    return serverRequest.getServletRequest().getSession();
                }
                return null;
            }
            @Override
            public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {}
        }).setHandshakeHandler(new DefaultHandshakeHandler() {
            /**
             * 指定握手主体生成规则,后续接收用户消息时会使用,默认用户频道为/UserDestinationPrefix/{Principal.getName}/频道
             * @param request
             * @param wsHandler
             * @param attributes
             * @return
             */
            @Nullable
            @Override
            protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
                return new UserPrincipal((User) attributes.get("user"));
            }
        //支持SockJS
        }).withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry) {

        messageBrokerRegistry.setApplicationDestinationPrefixes("/app");
// 客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息
 messageBrokerRegistry.enableSimpleBroker("/queue", "/topic");
        //指定用户频道前缀,默认为user可修改
        messageBrokerRegistry.setUserDestinationPrefix("/queue");
    }

    @Override
    public void configureWebSocketTransport(final WebSocketTransportRegistration registration) {
        registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
            @Override
            public WebSocketHandler decorate(final WebSocketHandler handler) {
                return new WebSocketHandlerDecorator(handler) {
                    @Override
                    public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
                        String username = session.getPrincipal().getName();
                        //上线相关操作
                        super.afterConnectionEstablished(session);
                    }

                    @Override
                    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)
                            throws Exception {
                        String username = session.getPrincipal().getName();
                      //离线相关操作
                        super.afterConnectionClosed(session, closeStatus);
                    }
                };
            }
        });
    }
}

服务端代码

** 说明 **

  • @MessageMapping("/sendMsg"),对应前端发送消息时调用的路径,访问路径为/ApplicationDestinationPrefixes/sendMsg,此时已与ContextPath无关。
  • convertAndSendToUser:向指定用户发送消息,对应设置中的determineUser,和指定的用户频道前缀,最终发送的路径:/用户频道前缀/Principal.getName/后缀
  • convertAndSend:向指定频道发送消息,可以使用@SendTo代替
  • @SendToUser:向请求的用户对应的用户频道发送消息,与convertAndSendToUser不能互换
    @MessageMapping("/sendMsg")
    public void sendMsg(Principal principal, String message) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        Message msg = JsonUtils.toObject(message, Message.class);
        try {
            msg.setSendTime(sdf.format(new Date()));
        } catch (Exception e) {
        }
        if (!"TO_ALL".equals(msg.getReceiver())) {
            template.convertAndSendToUser(msg.getReceiver(), "/chat", JsonUtils.toJson(msg));
        } else {
            template.convertAndSend("/notice", JsonUtils.toJson(msg));
        }
    }

前端代码

    function connect() {
        //连接端点,此时需加上项目路径
        var socket = new SockJS(_baseUrl+'any-socket');
        stompClient = Stomp.over(socket);
        stompClient.connect({}, function (frame) {
            //监听/topic/notice这个频道的消息
            stompClient.subscribe('/topic/notice', function (message) {
                showMessage(JSON.parse(message.body));
            });
            //监听当前登录用户这个频道的消息,对应服务端convertAndSendToUser
            stompClient.subscribe("/queue/"+_username+"/chat", function (message) {
                showMessage(JSON.parse(message.body));
            });
        });
    }

        $("#send").click(function () {
            debugger;
            var msg = {
                "username":_username
                ,"avatar":_avatar
                ,"content":$("#message").val()
                ,"receiver":target
            };;
            //向MessageMapping对应路径发送消息
            stompClient.send("/app/sendMsg", {}, JSON.stringify(msg));
            $("#message").val("");
        });

Demo

demo参考了这个博客,去掉了Spring Security的部分,修改了一对一消息发送的规则,写的比较简陋,第一个用户登录的时候会报错大家将就看看就行。第二个用户登录后刷新第一个登陆用户的页面会加载用户,就可以点对点的聊天了。 
https://gitee.com/MeiJM/stompDemo

参考资料

https://www.jianshu.com/p/4ef5004a1c81 
https://www.jianshu.com/p/0f498adb3820 
https://spring.io/guides/gs/messaging-stomp-websocket/ 
https://www.callicoder.com/spring-boot-websocket-chat-example/ 
http://www.ruanyifeng.com/blog/2017/05/websocket.html 
https://github.com/spring-guides/gs-messaging-stomp-websocket



【云栖快讯】阿里巴巴小程序繁星计划,20亿补贴第一弹云应用免费申请,限量从速!  详情请点击

网友评论