Websocket在Java中的实践——整合Rabbitmq和STOMP

大纲

  • Rabbitmq
    • 开启STOMP支持
  • 服务端
    • 依赖
    • 参数
    • 参数映射类
    • 配置类
    • 逻辑处理类
  • 测试
    • 测试页面
    • Controller
    • 测试案例

在《Websocket在Java中的实践——STOMP通信的最小Demo》一文中,我们使用enableSimpleBroker启用一个内置的内存级消息代理。本文我们将使用Rabbitmq作为消息代理,这样我们的服务就可以变成分布式部署。

Rabbitmq

开启STOMP支持

在Rabbitmq所在的机器上执行下面的命令:

sudo -H -u rabbitmq bash -c "/usr/lib/rabbitmq/bin/rabbitmq-plugins enable rabbitmq_stomp"

在这里插入图片描述
然后启动Rabbitmq

sudo service rabbitmq-server start

服务端

依赖

spring-boot-starter-websocket用于Websocket服务。
spring-boot-starter-amqp和spring-rabbit-stream都是用于Rabbitmq操作。
reactor-netty用于Broker。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit-stream</artifactId>
</dependency>
<dependency><groupId>io.projectreactor.netty</groupId><artifactId>reactor-netty</artifactId><version>1.1.20</version>
</dependency>

参数

src/main/resources/application.properties
需要注意的是,rabbitmq_stomp启动后会开启61613端口。

spring.rabbitmq.host=172.30.254.255
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=fangliang
spring.rabbitmq.stomp.port=61613

在这里插入图片描述
还有一点需要注意,很多文章上说使用guest用户登录。但是guest用户只能在Rabbitmq所在的机器上使用,如果跨机器使用会报下列错误。而且这和是否设置guest为全域无关。所以我们使用admin账户。

Received ERROR {message=[Bad CONNECT], content-type=[text/plain], version=[1.0,1.1,1.2], content-length=[26]} session=system text/plain payload=non-loopback access denied

spring.rabbitmq.stomp.port是一个自定义参数,它只是供Broker连接Rabbitmq使用。
spring.rabbitmq.port在当前本文例子中没有使用。

参数映射类

这个类主要是映射上述参数,方便后续使用。
src/main/java/com/nyctlc/stomprbmq/component/RabbitMQProperties.java

package com.nyctlc.stomprbmq.component;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class RabbitMQProperties {@Value("${spring.rabbitmq.password}")private String rabbitmqPassword;public String getRabbitmqPassword() {return rabbitmqPassword;}@Value("${spring.rabbitmq.username}")private String rabbitmqUsername;public String getRabbitmqUsername() {return rabbitmqUsername;}@Value("${spring.rabbitmq.host}")private String rabbitmqHost;public String getRabbitmqHost() {return rabbitmqHost;}@Value("${spring.rabbitmq.port}")private String rabbitmqPort;public String getRabbitmqPort() {return rabbitmqPort;}@Value("${spring.rabbitmq.stomp.port}")private String rabbitmqStompPort;public String getRabbitmqStompPort() {return rabbitmqStompPort;}
}

配置类

/handshake是STOMP和Websocket建立握手的接口。
enableStompBrokerRelay(“/topic”)会订阅Rabbitmq默认的交换器amq.topic的绑定关系中定义的队列。(所以我们看到很多文章订阅的前缀使用的是“topic”,而不用其他字段,这是有渊源的)
在这里插入图片描述
在这里插入图片描述

setRelayPort方法传递的是Rabbitmq的STOMP端口,即61613。
setClientLogin、setClientPasscode、setSystemLogin和setSystemPasscode都要设置为admin及其密码,否则会报错。

src/main/java/com/nyctlc/stomprbmq/config/WebSocketConfig.java

package com.nyctlc.stomprbmq.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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;import com.nyctlc.stomprbmq.component.RabbitMQProperties;@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Autowiredprivate RabbitMQProperties rabbitMQProperties;@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/handshake");}@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {registry.setApplicationDestinationPrefixes("/send");registry.enableStompBrokerRelay("/topic").setRelayHost(rabbitMQProperties.getRabbitmqHost()).setRelayPort(Integer.parseInt(rabbitMQProperties.getRabbitmqStompPort())).setClientLogin(rabbitMQProperties.getRabbitmqUsername()).setClientPasscode(rabbitMQProperties.getRabbitmqPassword()).setSystemLogin(rabbitMQProperties.getRabbitmqUsername()).setSystemPasscode(rabbitMQProperties.getRabbitmqPassword());}
}

逻辑处理类

这个类的handle方法会接受/send/msg-from-user端点发来的消息,然后转发给Rabbitmq的amp.topic交换器下msg-to-user路由键对应的队列。上述代码创建的Broker会持续监听这个队列,如果收到消息,则发送给客户端。

src/main/java/com/nyctlc/stomprbmq/controller/WebSocketController.java

package com.nyctlc.stomprbmq.controller;import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;@Controller
public class WebSocketController {@MessageMapping("/msg-from-user")@SendTo("/topic/msg-to-user")public String handle(String msg) {System.out.println("Received message: " + msg);return msg;}
}

测试

测试页面

src/main/resources/static/index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>STOMP over WebSocket Example with StompJs.Client</title><script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs"></script>
</head>
<body><h2>STOMP over WebSocket Example with StompJs.Client</h2><button id="connectButton">Connect</button><form id="messageForm"><input type="text" id="messageInput" placeholder="Type a message..."/><button type="submit">Send</button></form><div id="messages"></div><script>var client = null;function connect() {client = new StompJs.Client({brokerURL: 'ws://localhost:8080/handshake', // WebSocket服务端点connectHeaders: {},debug: function (str) {console.log(str);},reconnectDelay: 5000,heartbeatIncoming: 4000,heartbeatOutgoing: 4000,});client.onConnect = function(frame) {console.log('Connected: ' + frame);client.subscribe('/topic/msg-to-user', function(message) { // 订阅端点showMessageOutput(JSON.parse(message.body).content);});};client.onStompError = function(frame) {console.error('Broker reported error: ' + frame.headers['message']);console.error('Additional details: ' + frame.body);};client.activate();}function sendMessage(event) {event.preventDefault(); // 阻止表单默认提交行为var messageContent = document.getElementById('messageInput').value.trim();if(messageContent && client && client.connected) {var chatMessage = { content: messageContent };client.publish({destination: "/send/msg-from-user", body: JSON.stringify(chatMessage)}); // 发送端点document.getElementById('messageInput').value = '';}}function showMessageOutput(message) {var messagesDiv = document.getElementById('messages');var messageElement = document.createElement('div');messageElement.appendChild(document.createTextNode(message));messagesDiv.appendChild(messageElement);}document.getElementById('messageForm').addEventListener('submit', sendMessage);document.getElementById('connectButton').addEventListener('click', connect);</script>
</body>
</html>

Controller

这个Controller主要是为了让上述HTML可以通过URL访问。
src/main/java/com/nyctlc/stomprbmq/controller/FileController.java

package com.nyctlc.stomprbmq.controller;import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;@Controller
public class FileController {@GetMapping("/")public String index() {return "index"; // 返回index.html}@RequestMapping(value = "/favicon.ico")@ResponseStatus(value = HttpStatus.NO_CONTENT)public void favicon() {// No operation. Just to avoid 404 error for favicon.ico}
}

测试案例

在这里插入图片描述
在这里插入图片描述
我们在管理后台直接给这个队列发送消息,前端页面也会收到。比如我们发送{“content”:“message from management”}
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/43131.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Unity2D 2022:Particle System】添加拾取粒子特效

一、创建粒子特效游戏物体 二、修改粒子系统属性 1. 基础属性 &#xff08;1&#xff09;修改发射粒子持续时间&#xff08;Duration&#xff09;为3s &#xff08;2&#xff09;取消勾选循环&#xff08;Looping&#xff09; &#xff08;2&#xff09;修改粒子存在时间&…

面试常考题---128陷阱(详细)

1.问题引入 分别引入了int和Integer变量&#xff0c;并进行比较 int b 128; int b1 128;Integer d 127; Integer d1 127;Integer e 128; Integer e1 128;System.out.println(bb1); System.out.println(dd1); System.out.println(ee1); System.out.println(e.equals(e1)…

刷题(day01)

1、leetcode485.最大连续1的个数 给定一个二进制数组 nums &#xff0c; 计算其中最大连续 1 的个数。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,0,1,1,1] 输出&#xff1a;3 解释&#xff1a;开头的两位和最后的三位都是连续 1 &#xff0c;所以最大连续 1 的个数是 3.…

昇思第18天打卡|ShuffleNet图像分类

ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端&#xff0c;所以模型的设计目标就是利用有限的计算资源来达到最好的模型精度。ShuffleNetV1的设计核心是引入了两种操作&#xff1a;Pointw…

张大哥笔记:你一旦开窍,就会发现遍地都是钱

大家有没有发现&#xff0c;穷人总是追逐眼前的利益&#xff0c;总是在追着钱跑&#xff0c;却总是赚不到钱。而富人有着长远的见识&#xff0c;追着问题跑&#xff0c;最后却赚的盆满钵满。 我们听过这样一句话&#xff0c;钱不是赚来的&#xff0c;而是帮助别人解决问题后给你…

Qt/C++编写地图应用/离线地图下载/路径规划/轨迹回放/海量点/坐标转换

一、前言说明 这个地图组件写了很多年了&#xff0c;最初设计的比较粗糙&#xff0c;最开始只是为了满足项目需要&#xff0c;并没有考虑太多拓展性&#xff0c;比如最初都是按照百度地图写死在代码中&#xff0c;经过这几年大量的现场实际应用&#xff0c;以及大量的用户提出…

Django 新增数据 save()方法

1&#xff0c;添加模型 Test/app11/models.py from django.db import modelsclass Book(models.Model):title models.CharField(max_length100)author models.CharField(max_length100)publication_date models.DateField()price models.DecimalField(max_digits5, decim…

软件工程(上)

目录 软件过程模型&#xff08;软件开发模型&#xff09; 瀑布模型 原型模型 V模型 构件组装模型 螺旋模型&#xff08;原型瀑布&#xff09; 基于构件的软件工程&#xff08;CBSE&#xff09; 快速应用开发模型&#xff08;RAD&#xff09; 统一过程&#xff08;UP&a…

Linux学习看这一篇就够了,超超超牛的Linux基础入门

引言 小伙伴们&#xff0c;不管是学习c还是学习其他语言在我们学的路上都绕不过操作系统&#xff0c;而且&#xff0c;老生常谈的Linux更是每个计算机人的必修&#xff0c;那么我们对Linux的了解可能只是从别人那听到的简单的这个系统很牛&#xff0c;巴拉巴拉的&#xff0c;但…

小米rdemi红米ax3000t刷机 20240707最新配套完整程序整理合集

小米rdemi红米ax3000t刷机程序地址&#xff1a; https://www.123pan.com/s/LA1bVv-EOzVv.html 小米路由器SSH密码计算器 https://www.1234f.com/fuwu/ax3000t/ 最新更新地址&#xff1a;https://www.1234f.com/fuwu/openwrt/ 依次输入如下命令&#xff1a; curl -X POST h…

Leetcode 295.数据流的中位数

295.数据流的中位数 问题描述 中位数是有序整数列表中的中间值。如果列表的大小是偶数&#xff0c;则没有中间值&#xff0c;中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 。 实现 MedianFinder 类: Media…

算法013:水果成篮

水果成篮. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/fruit-into-baskets/ 这道题题目很长&#xff0c;仔细阅读过后&#xff0c;我们其实可以简化成&#xff…

聚焦数字创新,定义影像未来

国际数字影像产业园在明确产业定位与发展方向时&#xff0c;应聚焦于数字影像、文创、媒体等新兴产业领域&#xff0c;以技术创新为核心动力、产业升级为保障、市场拓展为途径、国际化发展为方向&#xff0c;推动园区的持续健康发展。 作为园区的核心产业&#xff0c;数字影像产…

第二证券股市知识:股票填权是怎么回事?利好还是利空?

1、股票填权的含义 股票填权是指在除权除息之后的一段时刻内&#xff0c;假设多数投资者看好该个股&#xff0c;股票的价格超过除权除息的基准价就叫做填权。上市公司假设能持续分红&#xff0c;就会向市场传递积极信号&#xff0c;招引更多投资者买入&#xff0c;越来越多的投…

使用Livox-Mid360激光雷达,复现FAST_LIO(保姆级教程)

前面我已经完成了mid360激光雷达的驱动安装&#xff0c;octomap的复现&#xff0c;昨天我去把这俩在正式环境中实测了一下&#xff0c;效果不好&#xff0c;走廊转角没建出来&#xff0c;我查了一下&#xff0c;应该是TF的原因&#xff0c;但这部分我还不太懂&#xff0c;看到有…

云计算【第一阶段(28)】DNS域名解析服务

一、DNS解析的定义与作用 1.1、DNS解析的定义 DNS解析&#xff08;Domain Name System Resolution&#xff09;是互联网服务中的一个核心环节&#xff0c;它负责将用户容易记住的域名转换成网络设备能够识别和使用的IP地址。一般来讲域名比 IP 地址更加的有含义、也更容易记住…

2024世界人工智能大会:deepin引领AI与操作系统融合新时代

内容来源&#xff1a;deepin&#xff08;深度&#xff09;社区 7月4日&#xff0c;WAIC 2024在上海拉开帷幕。大会围绕核心技术、智能终端、应用赋能三大板块&#xff0c;聚焦大模型、算力、机器人、自动驾驶等重点领域&#xff0c;集中展示一批“人工智能”创新应用最新成果。…

【web前端HTML+CSS+JS】--- JS学习笔记03

一、JS介绍 可以在前端页面上进行逻辑处理&#xff0c;来解决表单的验证等问题&#xff0c;提升效率&#xff0c;直接在前端提示问题&#xff0c;减少服务器压力 应用1&#xff1a;可以做静态验证和动态验证&#xff08;进行异步请求&#xff09; 应用2&#xff1a;可以解析后…

学习数据库2

在数据库中创建一个表student&#xff0c;用于存储学生信息 查看建表结果 向student表中添加一条新记录 记录中id字段的值为1&#xff0c;name字段的值为"monkey"&#xff0c;grade字段的值为98.5 并查看结果 向student表中添加多条新记录 2,"bob"…

鸿蒙开发小案例(名片管理))

鸿蒙开发小案例&#xff08;名片管理&#xff09; 1、页面效果1.1 初始页面1.2 点击名片展开1.3 点击收藏1.4 点击编辑按钮 2、实现代码2.1 DataModel.ets2.2 RandomUtil.ets2.3 ContactList.ets 1、页面效果 1.1 初始页面 1.2 点击名片展开 1.3 点击收藏 1.4 点击编辑按钮 2、…