Spring WebSocket 结合 RabbitMq

Spring WebSocket 结合 RabbitMq

参考链接

Spring 官文: https://docs.spring.io/spring-framework/docs/6.0.0-SNAPSHOT/reference/html/web.html#websocket-stomp-benefits

前端页面: https://github.com/callicoder/spring-boot-websocket-chat-demo

前言

阅读该篇文章请先浏览另一篇,该篇文章是基于其上的改进:
Springboot整合WebSocket(基于Stomp)

此外可以看下另外相关文章:

Springboot 整合 WebSocket 简单实现

Springboot整合RabbitMQ详解

RabbitMQ安装

Windows10安装
步骤
  • 到erlang官网下载win10版安装包。下载完成后傻瓜式安装。
  • 配置erlang环境变量

image-20211124135631736

image-20211124135719557

​ cmd输入erl验证安装是否成功,如下成功;ctrl+c退出

image-20211124135919422

  • 傻瓜式安装RabbitMQ服务。
    在RabbitMQ的gitHub项目中,下载window版本的服务端安装包
  • 进入安装目录,sbin目录下,执行:rabbitmq-plugins enable rabbitmq_management 命令安装管理页面的插件

image-20211124140316403

img

image-20211124140741917

同理配置RabbitMq环境变量,方便我们直接cmd输入命令

image-20220621175526473

RabbitMq安装 Stomp 插件
  1. cmd 进入 RabittMq 安装位置 /sbin 目录下,执行以下两个命令:
rabbitmq-plugins enable rabbitmq_stomp

rabbitmq-plugins enable rabbitmq_web_stomp
  1. 重启 RabbitMq 服务

    cmd 进入 RabittMq 安装位置 /bin 目录下,执行以下命令

# 停止 rabbitmq 服务
rabbitmq-service stop
# 启动 rabbitmq 服务
rabbitmq-service start
  1. 进入 RabbitMq 首页可以看到插件已安装

image-20220621175923426

RbbitMq Stomp 合法前缀约束

官方解释

https://www.rabbitmq.com/stomp.html

个人实验理解
/exchange 前缀

格式:/exchange/< exchangeName>/ < pattern>

解释:该前缀需要手动创建一个 exchangeName 的交换机,否则会出现错误。
/exchange 不适合使用现有队列中的消息。为每个订阅者创建一个新队列,并使用提供的路由键绑定到指定的交换。要使用现有队列,请使用 /amq/queue 。默认队列会在无人订阅时自动删除。

/queue 前缀

格式:/queue /< pattern>

解释:该前缀会发送到默认 amq.topic 交换机,创建的队列名由 queueName 指定。
如果未指定队列参数,则默认队列是持久的、非独占的、非自动删除的。
此外若有多个客户端订阅该队列,当队列存在消息时会被订阅该消息的客户端轮询消费,而不是所有客户端都消费该消息。

/topic 前缀

格式:/topic /< pattern>

解释:该前缀会使用默认 amq.topic 交换机,若没有符合 pattern 的队列,则创建一个随机队列名,队列的 routeKey/bindKey 即为pattern,默认队列会在无人订阅时自动删除。
可以使用 stomp.default_topic_exchange 指定交换机

/amq/queue 前缀

格式:/amq/queue/< name>

解释:该前缀会使用默认交换机,但需要我们自己创建 name 队列,否则会报错。
如果未指定队列参数,则默认队列是持久的、非独占的、非自动删除的。

/temp-queue/

创建一个临时队列(具有生成的名称),该队列专用于会话并自动订阅该队列。

Stomp 控制队列属性

同样见官文:https://www.rabbitmq.com/stomp.html

代码实现

依赖注入
<dependencies>

    <!-- web-socket -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>

    <!-- rabbitmq -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>

    <!-- Following additional dependency is required for Full Featured STOMP Broker Relay -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-reactor-netty</artifactId>
    </dependency>


    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>


</dependencies>
基于 Stomp 目的路径形式

服务端 Controller

@RestController
public class ChatController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 客户端发送消息入口,群发消息
     * @param chatMessage
     * @return
     */
    @MessageMapping("/chat/sendMessage")
    public void sendMessage(@Payload ChatMessage chatMessage) {
        // 发送到指定fanoutQueue队列
        messagingTemplate.convertAndSend("/amq/queue/fanoutQueue",chatMessage);
    }

    /**
     * 一对一消息发送
     * @param chatMessage
     */
    @PostMapping("/chat/single")
    public void sendSingleMessage(@RequestBody ChatMessage chatMessage) {       										messagingTemplate.convertAndSend("/topic/single"+chatMessage.getToUser(),chatMessage);
    }

    /**
     * 客户端新增用户消息入口,用于群发显示:新进入xx用户
     * @param chatMessage
     * @param headerAccessor
     * @return
     */
    @MessageMapping("/chat/addUser")
    public void addUser(@Payload ChatMessage chatMessage,
                               SimpMessageHeaderAccessor headerAccessor) {
        // Add username in web socket session
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        messagingTemplate.convertAndSend("/amq/queue/fanoutQueue",chatMessage);
    }

}

前端 js 订阅

function onConnected() {
    // 订阅群发主题,特定队列 fanoutQueue
    stompClient.subscribe('/amq/queue/fanoutQueue', onMessageReceived);
    
    // 订阅一对一主题,即通过用户名等唯一性标识拼接到订阅主题地址
    stompClient.subscribe('/exchange/WebSocket/single.' + username, onMessageReceived);

    stompClient.send("/app/chat/addUser",
        {},
        JSON.stringify({sender: username, type: 'JOIN'})
    )

    connectingElement.classList.add('hidden');
}
直接使用 RabbitTemplate

服务端发送消息改写

@RestController
public class ChatController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 客户端发送消息入口,群发消息(rabbitTemplate)
     * @param chatMessage
     * @return
     */
    @PostMapping("/chat/sendMessage")
    public void sendMsg(@RequestBody ChatMessage chatMessage) {
        String str = JSON.toJSONString(chatMessage);
        rabbitTemplate.convertAndSend("WebSocket","*",str);
    }

    /**
     * 一对一消息发送
     * @param chatMessage
     */
    @PostMapping("/chat/single")
    public void sendSingleMessage(@RequestBody ChatMessage chatMessage) {
        String str = JSON.toJSONString(chatMessage);
        rabbitTemplate.convertAndSend("WebSocket","single." + chatMessage.getToUser(),str);

    }

    /**
     * 客户端新增用户消息入口,用于群发显示:新进入xx用户
     * @param chatMessage
     * @param headerAccessor
     * @return
     */
    @MessageMapping("/chat/addUser")
    public void addUser(@Payload ChatMessage chatMessage,
                               SimpMessageHeaderAccessor headerAccessor) {
        // Add username in web socket session
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        messagingTemplate.convertAndSend("/topic/public",chatMessage);
    }

}

前端按照目标路径自行修改即可

function onConnected() {
    // 订阅群发主题
    stompClient.subscribe('/exchange/WebSocket/*',onMessageReceived)

    // 订阅一对一主题,即通过用户名等唯一性标识拼接到订阅主题地址
    stompClient.subscribe('/topic/single' + username,onMessageReceived)

    stompClient.send("/app/chat/addUser",
        {},
        JSON.stringify({sender: username, type: 'JOIN'})
    )

    connectingElement.classList.add('hidden');
}

集群 / 负载均衡简单看法

对于集群和负载均衡,我们无法通过简单的基于本机内存的 Stomp 实现,这时通过 RabbitMq 这种消息中间件就能很好的实现。当我们的 websocket 服务横向扩展时,因为我们是通过代理订阅 Mq 的主题,这样只要 Mq 主题上出现消息,订阅这个主题的客户端就会自然的收到消息推送。我们的代码也不需要做太大的变动,可以通过服务加入到注册中心,这样网关就能根据服务名负载均衡的去与 websocket 服务建立连接。

项目代码

最后附上所有的代码地址:https://gitee.com/ahang-gitee/websocket

若文章有误,欢迎评论区指正。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值