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

springboot2.0+websocket集成【群发消息+单对单】(二)的更多相关文章

  1. springboot2.0 快速集成kafka

    一.kafka搭建 参照<kafka搭建笔记> 二.版本 springboot版本 <parent> <groupId>org.springframework.bo ...

  2. springboot2.0+swagger集成

    场景:项目中添加Swagger配置,可以加速项目的开发,在快速开发项目中十分重要. 1.pom.xml添加swagger <!--swagger --> <dependency> ...

  3. SpringBoot2.0.2 不使用parent作为maven单继承方式操作 : org.springframework.boot : spring-boot-dependencies : 2.0.2.RELEASE

    1.pom配置方式 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="h ...

  4. SpringBoot2.0整合fastjson的正确姿势

            SpringBoot2.0如何集成fastjson?在网上查了一堆资料,但是各文章的说法不一,有些还是错的,可能只是简单测试一下就认为ok了,最后有没生效都不知道.恰逢公司项目需要将J ...

  5. 模拟websocket推送消息服务mock工具二

    模拟websocket推送消息服务mock工具二 在上一篇博文中有提到<使用electron开发一个h5的客户端应用创建http服务模拟后端接口mock>使用electron创建一个模拟后 ...

  6. SpringBoot2.0集成WebSocket,实现后台向前端推送信息

    感谢作者,支持原创: https://blog.csdn.net/moshowgame/article/details/80275084 什么是WebSocket? WebSocket协议是基于TCP ...

  7. springboot2.0集成webSocket

    WebSocket和http的区别? http协议是用在应用层的协议,他是基于tcp协议的,http协议建立链接也必须要有三次握手才能发送信息. http链接分为短链接,长链接,短链接是每次请求都要三 ...

  8. SpringBoot2.0整合WebSocket,实现后端数据实时推送!

    之前公司的某个系统为了实现推送技术,所用的技术都是Ajax轮询,这种方式浏览器需要不断的向服务器发出请求,显然这样会浪费很多的带宽等资源,所以研究了下WebSocket,本文将详细介绍下. 一.什么是 ...

  9. Springboot:SpringBoot2.0整合WebSocket,实现后端数据实时推送!

    一.什么是WebSocket? B/S结构的软件项目中有时客户端需要实时的获得服务器消息,但默认HTTP协议只支持请求响应模式,这样做可以简化Web服务器,减少服务器的负担,加快响应速度,因为服务器不 ...

随机推荐

  1. 奇异值分解(SVD)(基础知识)

    参考:https://www.cnblogs.com/pinard/p/6251584.html 参考:http://blog.csdn.net/u010099080/article/details/ ...

  2. 配置Android Studio

    1.去gradle官网下载gradle,gradle的版本可以在C:\Program Files\Android\Android Studio\gradle下看到 2.新建一个项目,退出后把下载好的g ...

  3. 理解性能的奥秘——应用程序中慢,SSMS中快(4)收集解决参数嗅探问题的信息

    ---从计划缓存中直接获取查询计划和参数: ), ) SELECT @dbname = 'hydee_连锁', @procname = 'dbo.p_select_ware'; WITH baseda ...

  4. USACO 2006 November Gold Corn Fields

    USACO 2006 November Gold Corn Fields 题目描述: Farmer John has purchased a lush new rectangular pasture ...

  5. Harbor修改暴露端口

    把原来的端口映射改成1180 一 修改docker-compose.yml [root@topcheer ~]# cat /mnt/harbor/docker-compose.yml version: ...

  6. Flask学习 2修改路由规则 传入参数访问url

    #!/usr/bin/env python # encoding: utf-8 """ @version: v1.0 @author: cxa @file: flask0 ...

  7. Linux C编程

    Linux C网络编程 1.Linux套接字 1.1 套接字介绍 套接字(Sockets),即为网络进程ID,是由运行这个进程的计算机的IP地址和这个进程使用的端口(Port)组成. 可以只用'net ...

  8. python如何判断1个列表中所有的数据都是相等的?

    方法一: 元素两两比较,如果有数据不同,则r的值变为false #!/usr/bin/python a=[22,22,22,22] b = len(a) r=True for i in range(b ...

  9. 云计算openstack共享组件——Memcache 缓存系统

    一.缓存系统 静态web页面: 1.工作流程: 在静态Web程序中,客户端使用Web浏览器(IE.FireFox等)经过网络(Network)连接到服务器上,使用HTTP协议发起一个请求(Reques ...

  10. POJ 3281 网络流 拆点保证本身只匹配一对食物和饮料

    如何建图? 最开始的问题就是,怎么表示一只牛有了食物和饮料呢? 后来发现可以先将食物与牛匹配,牛再去和饮料匹配,实际上这就构成了三个层次. 起点到食物层边的容量是1,食物层到奶牛层容量是1,奶牛层到饮 ...