WebSocket的那些事(3-STOMP实操篇)
目录
- 一、序言
- 二、STOMP详解
- 1、STOMP简单介绍
- 2、STOMP协议内容
- 3、使用STOMP的好处
- 三、代码示例
- 1、Maven依赖
- 2、开启WebSocket消息代理
- 3、控制器
- 4、前端页面greeting.html
- 四、测试
- 1、连接服务端
- 2、发送消息
- 五、STOMP消息传播流程
- 六、结语
一、序言
上节中我们在 WebSocket的那些事(2-实操篇)中简单介绍了Spring中对于WebSocket的封装,并实现一个简单的服务端,这节我们将会结合STOMP子协议实现WebSocket通信。
二、STOMP详解
1、STOMP简单介绍
WebSocket协议定义了两种消息类型(文本类型和二进制类型),但是消息内容却是未定义的,下面我们介绍一下STOMP协议。
STOMP (Simple Text Oriented Messaging Protocol) 起源于脚本语言,比如Ruby、Python和Perl,用于连接企业消息代理,它可以用于任何可靠的双向网络协议中,如TCP和WebSocket。尽管STOMP是一个面向文本的协议,但消息负载可以是文本或者二进制。
STOMP基于WebSocket在客户端和服务端之间定义了一种机制,协商通过子协议(更高级的消息协议)来定义可以发送何种消息,每条消息的内容是什么,等等。
2、STOMP协议内容
STOMP是一个基于帧的协议,帧的结构如下:
COMMAND
header1:value1
header2:value2Body
客户端可以用SEND或者SUBSCRIBE命令去发送和订阅消息,destination头部用来描述消息发送到哪里以及谁应该接收消息,下面的消息结构是客户端订阅股票行情的例子,如下:
SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*
下面的消息结构是客户端发送交易请求的例子,如下:
SEND
destination:/queue/trade
content-type:application/json
content-length:44{"action":"BUY","ticker":"MMM","shares",44}
STOMP服务端可以使用MESSAGE 命令广播消息给所有的订阅者,下面的例子为广播股票行情消息给所有消息订阅者。
MESSAGE
message-id:nxahklf6-1
subscription:sub-1
destination:/topic/price.stock.MMM{"ticker":"MMM","price":129.45}
3、使用STOMP的好处
在Spring中使用STOMP与原生WebSockets相比提供了更加丰富的编程模型,下面是使用STOMP的优点:
- 不需要发明自定义消息协议和消息格式。
- 在Spring中,STOMP客户端,包括Java客户端都可用。
- 可使用其它消息代理管理消息订阅和广播,如
RabbitMQ,ActiveMQ等支持STOMP协议的中间件。 - 应用逻辑处理入口可以像Spring MVC一样统一在
@Controller实例内,同时消息可以基于STOMP头部消息进行路由,而不是直接用WebSocketHandler处理原生WebSocket消息。 - 可以基于STOMP目的地和消息类型使用
Spring Security对消息进行安全传输。
三、代码示例
1、Maven依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
备注:这里我就简单用Thymeleaf作模板引擎,为了方便调试在application.yml中将
spring.thymeleaf.cache设为false来禁用模板缓存。
2、开启WebSocket消息代理
@Configuration
@EnableWebSocketMessageBroker
public class WebsocketMessageBrokerConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/websocket") // WebSocket握手端口.addInterceptors(new HttpSessionHandshakeInterceptor()).setAllowedOriginPatterns("*") // 设置跨域.withSockJS(); // 开启SockJS回退机制}@Overridepublic void configureWebSocketTransport(WebSocketTransportRegistration registry) {// 这里我们设置入站消息最大为8Kregistry.setMessageSizeLimit(8 * 1024);}@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {registry.setApplicationDestinationPrefixes("/app") // 发送到服务端目的地前缀.enableSimpleBroker("/topic");// 开启简单消息代理,指定消息订阅前缀}}
备注:
- 关于SockJS的介绍请参考上篇文章 WebSocket的那些事(1-概念篇)最后部分。
setApplicationDestinationPrefixes的意思是以目的地以/app开头的消息将会被路由到Controller实例中的方法进行处理。enableSimpleBroker将会启用一个内置的内存消息代理,用于订阅、广播和路由消息目的地到以/topic开头代理中。
3、控制器
@Slf4j
@Controller
public class GreetingController {@GetMapping("/page/greeting")public ModelAndView turnToGreetingPage() {return new ModelAndView("/greeting");}@MessageMapping("/greeting")public String sayGreeting(String name) {log.info("Message received: {}", name);return "Hello, " + name;}
}
4、前端页面greeting.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>greeting</title><script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script><style>#mainWrapper {width: 600px;margin: auto;}</style>
</head>
<body>
<div id="mainWrapper"><div id="msgWrapper"><p style="vertical-align: top">发送的消息:</p><textarea id="msgSent" style="width: 400px;height: 100px"></textarea><p style="vertical-align: top">收到的消息:</p><textarea id="msgReceived" style="width: 400px;height: 100px"></textarea></div><div style="margin-top: 5px;"><button onclick="connect()">连接</button><button onclick="sendMessage()">发送消息</button><button onclick="disconnect()">断开连接</button></div>
</div>
<script type="text/javascript">$(() => {$('#msgSent').val('');$("#msgReceived").val('');});let stompClient = null;// 连接服务器const connect = () => {const ws = new SockJS('http://localhost:8080/websocket');stompClient = Stomp.over(ws);stompClient.connect({}, () => {$("#msgReceived").val('Connected to websocket server!');stompClient.subscribe('/topic/greeting', function (message) {console.log(`Received message: ${message.body}`);$("#msgReceived").val(message.body);});});}// 断连const disconnect = () => {stompClient.disconnect(() => {$("#msgReceived").val('Disconnected from WebSocket server');});}// 发送消息,这里直接发的是文本const sendMessage = () => {stompClient.send('/app/greeting', {}, $('#msgSent').val());}
</script>
</body>
</html>
备注:
- greeting.html为
Thymeleaf模板页面,文件直接放在类路径templates目录下即可.- 这里我们选用了
SockJS去连接服务端,为什么使用SockJS请参考 WebSocket的那些事(1-概念篇),同时注意协议名称是http而不是ws。
四、测试
浏览器输入http://localhost:8080/page/greeting即可跳转到我们的页面,如下:

1、连接服务端
点击连接按钮后,我们发现已经与服务端成功建立连接。我们可以观察到客户端发送了两条命令,一条是Connect建立连接命令,另一条是连接建立成功后的SUBSCRIBE命令,用来订阅目的地以/topic/greeting开头的消息。

2、发送消息
点击发送按钮发送消息,可以看到服务端也返回了Hello, Nick的回复消息。

备注:我们可以看到客户端发送了一条
SEND命令,目的地为/app/greeting,内容长度为4个字节。同时服务端回复了一条MESSAGE命令,这条命令的意思是广播消息给所有订阅/topic/greeting的客户端。
五、STOMP消息传播流程
一旦STOMP端点暴露,Spring应用对于连接的客户端就变成了STOMP代理,下面我们简单介绍一下服务端消息传播流程。

上面的图展示了三种消息渠道:
clientInboundChannel: 客户端消息入站通道,用来传递WebSocket客户端接收的消息。clientOutboundChannel: 客户端消息出站通道,用来发送服务端消息给WebSocket客户端。brokerChannel: 消息代理通道,用来在服务层传递消息给消息代理。
目的地以/app/a开头的消息会通过clientInboundChannel路由到MessageHandler消息处理器进行处理,然后再将消息通过brokerChannel发送到SimpleBroker中。
目的地以/topic/a开头的消息会通过clientInboundChannel直接路由到SimpleBroker中进行处理。
上图中的SimpleBroker实际上是Spring基于内存的内置消息代理,实际上Spring也支持集成其它支持STOMP协议的MQ,如ActiveMQ、RabbitMQ等,集成外部消息代理的消息传播流程图如下:

从上图中,我们可以看到StompBrokerRelay会和外部消息代理进行通信,通过enableStompBrokerRelay即可以集成外部消息代理,关于集成外部消息代理的例子和细节我们在后面的章节在讨论。
六、结语
这节我们简单介绍了STOMP协议、Spring中对WebSocket的STOMP支持以及具体的代码集成示例。
下节我们将会具体介绍@Controller控制器中相关消息注解的使用示例和细节,如@MessageMapping、@SubscribeMapping、@MessageExceptionHandler、@SendTo、@SendToUser等。

相关文章:
WebSocket的那些事(3-STOMP实操篇)
目录 一、序言二、STOMP详解1、STOMP简单介绍2、STOMP协议内容3、使用STOMP的好处 三、代码示例1、Maven依赖2、开启WebSocket消息代理3、控制器4、前端页面greeting.html 四、测试1、连接服务端2、发送消息 五、STOMP消息传播流程六、结语 一、序言 上节中我们在 WebSocket的…...
《花雕学AI》WeTab+ChatGPT:让浏览器变成你的智能助手
引言: 浏览器是我们日常使用的最重要的工具之一,它可以帮助我们获取信息、娱乐、学习、工作等。但是,传统的浏览器往往不能满足我们的个性化需求,也不能给我们提供智能化的服务。那么,有没有一种浏览器可以让我们的体…...
MySQL 字段为 NULL 的5大坑,大部分人踩过
数据库字段允许空值(null)的问题,小伙伴你遇到过吗? 在验证问题之前,我们先建一张测试表及测试数据。 构建的测试数据,如下图所示: 有了上面的表及数据之后,我们就来看当列中存在 NULL 值时,究…...
Android SystemUI篇(二)
目录 一、简介 二、SystemUI的架构 三、SystemUI的主要组件 四、SystemUI的主要功能 五、SystemUI的自定义和定制 六、SystemUI的性能优化 一、简介 SystemUI是Android操作系统的一个关键组件,主要负责管理和提供用户界面的核心元素,如状态栏、导航…...
第六讲:“声音”写具体
爸爸又打呼了!“呼噜一呼噜一像一股巨浪腾空而起,以每秒八十米的速度向上冲刺,力图掀开天花板,掀翻整座住宅楼;“呼噜一一呼噜一-”,像一台轰鸣的坦克在穿行,床垫在抖动,吊灯在摇晃,墙灰在簌籁(…...
Unity 向量
向量的加减法本文就不再赘述了,本文侧重介绍脚本中的向量写法 一、向量的数乘 定义:k(x,y,z)(kx,ky,kz) 若向量长度为L,k取1/L,就恰好能让原向量长度变成1,变成了单位向量,这称为向量的标准化 。 由于长度…...
Apache Tomcat AJP协议文件读取与包含
永远也不要忘记能够笑的坚强,就算受伤,我也从不彷徨。 0x01.漏洞情况分析 Tomcat是Apache软件基金会Jakarta 项目中的一个核心项目,作为目前比较流行的Web应用服务器,深受Java爱好者的喜爱,并得到了部分软件开发商的…...
实验10 人工神经网络(1)
1. 实验目的 ①理解并掌握误差反向传播算法; ②能够使用单层和多层神经网络,完成多分类任务; ③了解常用的激活函数。 2. 实验内容 ①设计单层和多层神经网络结构,并使用TensorFlow建立模型,完成多分类任务…...
OPPO关停哲库业务,工程师造芯何去何从?
5月12日(上周五),新浪科技从OPPO处了解到,OPPO将终止ZEKU业务。3000多人团队突然原地解散,网上唏嘘声一片! ZEKU最初成立于2019年,是OPPO的全资子公司,欧加集团百分之百注资成立。总…...
面试被问麻了....
前几天组了一个软件测试面试的群,没想到效果直接拉满,看来大家对面试这块的需求还是挺迫切的。昨天我就看到群友们发的一些面经,感觉非常有参考价值,于是我就问他还有没有。 结果他给我整理了一份非常硬核的面筋,打开…...
AspNetCore中的配置文件详解
1 配置文件 程序开发中,有些信息是要根据环境改变的,比如开发环境的数据库可能是本地数据,而生产环境下需要连接生产数据库,我们需要把这些信息放到程序外面,在程序运行时通过读取这些外部信息实现不改变程序代码适应…...
实时更新天气微信小程序开发
1.新建一个天气weather项目 2.在app.json中创建一个路由页面 当我们点击保存的时候,微信小程序会自动的帮我们创建好页面 3.在weather页面上书写我们的骨架 4.此时我们的页面很怪,因为没有给它添加样式和值。此时我们给它一个样式。(样式写在…...
css渐变
线性渐变 liner-gradient属性值用来设置线性渐变,第一个参数值是方向,默认是从上往下,往后就是渐变颜色的种类。 background-image:liner-gradient(方向,颜色1,颜色2...) .box {display: flex;width: 400px;height: …...
《斯坦福数据挖掘教程·第三版》读书笔记(英文版) Chapter 2 MapReduce and the New Software Stack
来源:《斯坦福数据挖掘教程第三版》对应的公开英文书和PPT Chapter 2 MapReduce and the New Software Stack Computing cluster means large collections of commodity hardware, including conventional processors (“compute nodes”) connected by Ethernet …...
HTML零基础快速入门(详细教程)
1,HTML代码特点 <html><head></head><body>hello world!</body> </html>HTML代码有以下特点: html代码是通过标签来组织的,而标签是由尖括号< >组织的,也可被叫作元素(ele…...
Kubernetes第5天
第七章 Service详解 本章节主要介绍kubernetes的流量负载组件:Service和Ingress。 Service介绍 在kubernetes中,pod是应用程序的载体,我们可以通过pod的ip来访问应用程序,但是pod的ip地址不是固定的,这也就意味着…...
RK3568平台开发系列讲解(调试篇)debugfs 分析手段
🚀返回专栏总目录 文章目录 一、enable debugfs二、debugfs API三、使用示例沉淀、分享、成长,让自己和他人都能有所收获!😄 📢Linux 上有一些典型的问题分析手段,从这些基本的分析方法入手,你可以一步步判断出问题根因。这些分析手段,可以简单地归纳为下图: 从这…...
【Spring框架全系列】SpringBoot配置日志文件
🍧🍧哈喽,大家好,我是小浪。那么上篇博客我们学习了SpringBoot配置文件的相关操作,本篇博客我们将学习一个新的知识点,SpringBoot日志文件。🖥🖥 📲目录 一、日志是什么…...
事务 ---MySQL的总结(六)
事务 多进程进行并改变同一个数据,如果没有进行版本控制,就会出现数据不确定的问题,为此引入了事务的概念。可以进行数据回滚,解决潜在的问题。 事务的概念 一组的DML组成,这一些的DML要么同时成功,要么同…...
22 标准模板库STL之容器适配器
概述 提到适配器,我们的第一印象是想到设计模式中的适配器模式:将一个类的接口转化为另一个类的接口,使原本不兼容而不能合作的两个类,可以一起工作。STL中的容器适配器与此类似,是一个封装了序列容器的类模板,它在一般序列容器的基础上提供了一些不同的功能和接口。之所…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
