zoukankan      html  css  js  c++  java
  • springboot2.0+websocket集成【群发消息+单对单】(二)

    https://blog.csdn.net/qq_21019419/article/details/82804921

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

    第二篇,主要是使用socketjs,stomp模式的websocket简单实现。
    第一篇的地址:springboot2.0+websocket集成【群发消息+单对单】
    参考:
    http://tech.lede.com/2017/03/08/qa/websocket+spring/
    https://blog.csdn.net/mr_zhuqiang/article/details/46618197

    继续上次的项目。如果对下面的代码有部分看不明白的,请到上一篇看看流程,或者到文末贴出项目的git地址。
    1. 先从配置开始,WebStompConfig

    代码中的注释基本能够解释清楚每行的意思了,这里就不再细说
    完整代码

    package com.example.websocketdemo1.stomp;

    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;

    /**
     * EnableWebSocketMessageBroker 注解表明: 这个配置类不仅配置了 WebSocket,还配置了基于代理的 STOMP 消息;
     * registerStompEndpoints() 方法:添加一个服务端点,来接收客户端的连接。将 “/chat” 路径注册为 STOMP 端点。这个路径与之前发送和接收消息的目的路径有所不同, 这是一个端点,客户端在订阅或发布消息到目的地址前,要连接该端点,即用户发送请求 :url=’/127.0.0.1:8080/chat’ 与 STOMP server 进行连接,之后再转发到订阅url;
     * configureMessageBroker() 方法:配置了一个 简单的消息代理,通俗一点讲就是设置消息连接请求的各种规范信息。
     *
     * @author linyun
     * @date 2018/9/13 下午5:15
     */
    @Configuration
    @EnableWebSocketMessageBroker
    public class WebStompConfig implements WebSocketMessageBrokerConfigurer {

        @Autowired
        private WebSocketHandleInterceptor interceptor;

        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            //添加一个/chat端点,客户端就可以通过这个端点来进行连接;withSockJS作用是添加SockJS支持
            registry.addEndpoint("/chat").setAllowedOrigins("*").withSockJS();
        }

        @Override
        public void configureMessageBroker(MessageBrokerRegistry registry) {
            //定义了两个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息
            registry.enableSimpleBroker("/message", "/notice");
            //定义了服务端接收地址的前缀,也即客户端给服务端发消息的地址前缀
            registry.setApplicationDestinationPrefixes("/app");
        }

        @Override
        public void configureClientInboundChannel(ChannelRegistration registration) {
            //注册了一个接受客户端消息通道拦截器
            registration.interceptors(interceptor);
        }
    }

        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

        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

    2. 用户信息注册,WebSocketHandleInterceptor

    上一篇里面,用户信息我们是直接存储到session中,然后再通过握手的时候,将用户信息存入WebSocketSession。
    这次使用stomp的模式也存在一个单对单的发送消息,就需要知道对方是谁,所以也要注册一下用户信息。

    完整的代码

    package com.example.websocketdemo1.stomp;

    import com.sun.security.auth.UserPrincipal;
    import org.springframework.messaging.Message;
    import org.springframework.messaging.MessageChannel;
    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.MessageHeaderAccessor;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;

    import java.security.Principal;

    /**
     * @author linyun
     * @date 2018/9/13 下午5:57
     */
    @Component
    public class WebSocketHandleInterceptor implements ChannelInterceptor {

        /**
         * 绑定user到websocket conn上
         * @param message
         * @param channel
         * @return
         */
        @Override
        public Message<?> preSend(Message<?> message, MessageChannel channel) {
            StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
            if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                String username = accessor.getFirstNativeHeader("username");
                if (StringUtils.isEmpty(username)) {
                    return null;
                }
                // 绑定user
                Principal principal = new UserPrincipal(username);
                accessor.setUser(principal);
            }
            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

        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

    注意这里的username信息

    String username = accessor.getFirstNativeHeader("username");

        1

        1

    username是在页面中传递来的,具体的传递方式在后面的页面中,具体的参数名称可以随意自定义。
    另外一种获取用户信息的方式:

    Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
    if (raw instanceof Map) {
        System.out.println(raw);
        // 打印raw之后,可以查看头部的参数,包含了username。
    }

        1
        2
        3
        4
        5

        1
        2
        3
        4
        5

    3.处理消息的类,GreetingController

    用来接收和发送消息。
    先来一个消息的model,用来包装消息,使用lombok插件,省去了getset了。
    代码:

    package com.example.websocketdemo1.stomp;

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    /**
     * @author linyun
     * @date 2018/9/13 下午5:44
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class Message {
        private String to;
        private Long date;
        private String from;
        private String content;
    }

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

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

    controller的完整代码

    package com.example.websocketdemo1.stomp;

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.messaging.Message;
    import org.springframework.messaging.MessageHeaders;
    import org.springframework.messaging.handler.annotation.*;
    import org.springframework.messaging.simp.SimpMessagingTemplate;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;

    import java.security.Principal;
    import java.util.Map;

    /**
     * @author linyun
     * @date 2018/9/13 下午5:42
     */
    @Slf4j
    @Controller
    public class GreetingController {

        @Autowired
        private SimpMessagingTemplate simpMessagingTemplate;


        /**
         * 测试页面
         * @return
         */
        @RequestMapping("/chat4")
        public String chat4() {
            return "chat4";
        }

        /**
         * 测试页面2
         * @return
         */
        @RequestMapping("/chat5")
        public String chat5() {
            return "chat5";
        }

        /**
         * 测试订阅
         * @param message
         * @param messageHeaders
         * @param destination
         * @param headers
         * @param id
         * @param body
         */
        @MessageMapping("/hello/{id}")
        public void hello(Message message,
                          MessageHeaders messageHeaders,
                          @Header("destination") String destination,
                          @Headers Map<String, Object> headers,
                          @DestinationVariable long id,
                          @Payload String body) {
            log.info("message:{}", message);
            log.info("messageHeaders:{}", messageHeaders);
            log.info("destination:{}", destination);
            log.info("headers:{}", headers);
            log.info("id:{}", id);
            log.info("body:{}", body);
        }


        /***  群消息   ***/

        /**
         * 主动返回消息。
         * @param message
         */
        @MessageMapping("/hello")
        public void hello(@Payload com.example.websocketdemo1.stomp.Message message) {
            System.out.println(message);
            com.example.websocketdemo1.stomp.Message returnMessage = new com.example.websocketdemo1.stomp.Message();
            returnMessage.setContent("转发," + message.getContent());
            simpMessagingTemplate.convertAndSend("/message/public", returnMessage);
        }

        /**
         * 使用注解的方式返回消息
         * @param message
         * @return
         */
        @MessageMapping("/hello1")
        @SendTo("/message/public")
        public com.example.websocketdemo1.stomp.Message hello1(@Payload com.example.websocketdemo1.stomp.Message message) {
            System.out.println(message);
            com.example.websocketdemo1.stomp.Message returnMessage = new com.example.websocketdemo1.stomp.Message();
            returnMessage.setContent("转发2," + message.getContent());
            return returnMessage;
        }

        /***  点对点   ***/

        /**
         * 点对点发送消息。接收消息的人是从消息中获取的。
         * @param message
         * @param principal
         */
        @MessageMapping("/hello2")
        public void hello2(@Payload com.example.websocketdemo1.stomp.Message message, Principal principal) {
            System.out.println(message);
            System.out.println(principal);
            com.example.websocketdemo1.stomp.Message returnMessage = new com.example.websocketdemo1.stomp.Message();
            returnMessage.setContent("转发3," + message.getContent());
            simpMessagingTemplate.convertAndSendToUser(message.getTo(), "/notice/msg", returnMessage);
        }

    }

        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
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114

        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
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114

    稍微解释一下代码中的几个方法
    第一个方法,/hello/{id},主要是用来测试在一次发送消息的请求中能够获取到那些参数,合理的利用这些参数于自己的业务中。

        @MessageMapping("/hello/{id}")
        public void hello(Message message,
                          MessageHeaders messageHeaders,
                          @Header("destination") String destination,
                          @Headers Map<String, Object> headers,
                          @DestinationVariable long id,
                          @Payload String body) {
            log.info("message:{}", message);
            log.info("messageHeaders:{}", messageHeaders);
            log.info("destination:{}", destination);
            log.info("headers:{}", headers);
            log.info("id:{}", id);
            log.info("body:{}", body);
        }

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

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

    4.结合页面测试

    新建2个页面,页面中设置用户的信息

    http://127.0.0.1:8080/chat4
    username='tom';
    http://127.0.0.1:8080/chat5
    username='jerry';

        1
        2
        3
        4

    主要是引入 stomp.js和socketjs这2个js。
    页面完整代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title>测试websocket</title>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.css">
    </head>
    <body>
    <div class="container">
        <button type="button" class="btn btn-primary" onclick="connect()">链接</button>
        <button type="button" class="btn btn-primary" onclick="disconnect()">断开</button>

    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.js"></script>
    <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <script language=javascript>

        var username = 'tom';
        var sendMessage = null;
        var disConnect = null;

        function connect() {
            var socket = new SockJS("http://127.0.0.1:8080/chat");
            var client = Stomp.over(socket);
            client.connect({
                username: username
            }, function (succ) {
                console.log('client connect success:', succ);

                client.subscribe("/message/public", function (res) {
                    console.log('收到消息---/message/public:',res);
                });

                client.subscribe("/user/notice/msg", function (res) {
                    console.log('个人消息:',res)
                });
            }, function (error) {
                console.log('client connect error:', error);
            });
            sendMessage = function (destination, headers, body) {
                client.send(destination, headers, body)
            };
            disConnect = function () {
                client.disconnect();
                console.log('client connect break')
            }
        }

        function disconnect() {
            disConnect();
        }

        //发送聊天信息
        function send(roomId, ct) {
            var messageModel = {};
            messageModel.type = 1;
            messageModel.content = ct;
            messageModel.from = username;
            sendMessage("/app/hello/" + roomId, {}, JSON.stringify(messageModel));
        }

        /**
         * 测试发送一个消息,如果订阅了/sub/public的用户都会收到消息。
         */
        function send1() {
            var messageModel = {};
            messageModel.type = 1;
            messageModel.content = '你好,' + new Date().getTime();
            messageModel.from = username;
            sendMessage("/app/hello", {}, JSON.stringify(messageModel));
        }
        function send2() {
            var messageModel = {};
            messageModel.type = 1;
            messageModel.content = 'hello1,' + new Date().getTime();
            messageModel.from = username;
            sendMessage("/app/hello1", {}, JSON.stringify(messageModel));
        }
        /** 发送消息给个人,接收者 to **/
        function send3() {
            var messageModel = {};
            messageModel.to = 'jerry';
            messageModel.type = 1;
            messageModel.content = 'hello1,' + new Date().getTime();
            messageModel.from = username;
            sendMessage("/app/hello2", {}, JSON.stringify(messageModel));
        }
        }
    </script>
    </body>
    </html>

        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
        93
        94
        95
        96
        97
        98

        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
        93
        94
        95
        96
        97
        98

    点击链接按钮,主要是做了几个操作,

        1. 链接到websocket
        2. 订阅/message/public
        3. 订阅/user/notice/msg

    5. 跑起来测试

    进入页面后,打开控制台,直接输入命令。如果觉得不方便,可以在页面加几个按钮,美观点=)

            send(‘123456’,‘hello’);

    此方法会发起一个消息,推送到/app/hello/123456,这个地址,并带上参数messageModel。
    监控到后台 @MessageMapping("/hello/{id}") 接收到的消息。

            send1();

    发送一条消息给/app/hello,后台接收到之后通过 simpMessagingTemplate.convertAndSend("/message/public", returnMessage); 广播一条消息给所有订阅了/message/public的用户。
    所以为了测试,最好多开几个浏览器。观察一下console的打印信息。

            send3();

    发送一条消息给 @MessageMapping("/hello2") ,注意这里的消息messageModel中加入了to=jerry。后台接收到参数之后,使用 simpMessagingTemplate.convertAndSendToUser(message.getTo(), “/notice/msg”, returnMessage); 将消息发送给jerry,从而实现了单对单的消息推送。
    6.总结一下

        stomp底层实现都是广播,单对单只是表面看起来特殊一点,本质其实也是生成的一个唯一的广播地址。测试也简单,打开2个页面登录tom,一个登录jerry。用jerry发送消息给tom。2个tom都会收到消息。
        页面中注册订阅地址,后台通过注册的地址发送广播。所以/message和/notice后面的参数其实可以随便写。

    完整项目 git:https://gitlab.com/tulongx/websocketdemo1

    以上。

    ---------------------
    版权声明:本文为CSDN博主「暴躁兔子」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_21019419/article/details/82804921

  • 相关阅读:
    解决“google快照无法打开”的简单而有效的方法~
    在Struts2里面嵌入Spring
    HDU
    设计模式大总结(二)
    Node.js入门笔记
    草图检索和识别[开源]
    2019-10-31-VisualStudio-断点调试详解
    2019-10-31-VisualStudio-断点调试详解
    2019-9-2-C#-设计模式-责任链
    2019-9-2-C#-设计模式-责任链
  • 原文地址:https://www.cnblogs.com/xiang--liu/p/11364956.html
Copyright © 2011-2022 走看看