☰
Spring WebSocket(STOMP)
2026/10/14 9:19:36 网站建设 项目流程

Spring WebSocket(STOMP)

    • 一句话定义
    • 一、核心原理
    • 二、STOMP常用指令帧
    • 三、目的地destination前缀含义(面试重点)
    • 四、核心注解
    • 五、后端完整配置类
    • 六、Controller业务代码示例
      • 示例1:广播消息(群聊,@SendTo)
      • 示例2:一对一私聊(@SendToUser)
    • 七、前端 stomp.js 完整示例
    • 八、SockJS是什么
    • 九、心跳机制 HeartBeat(生产环境必备)
    • 十、两种消息Broker对比
      • SimpleBroker(内置内存Broker)
    • 十一、踩坑点与注意事项
    • 十二、适用场景
    • 十三、面试精简背诵版
    • 十四、高频面试题

一句话定义

WebSocket:底层双向长连接协议,通过HTTP握手升级协议,建立TCP长通道。传统HTTP只能前端发起请求、后端响应;WebSocket建立连接之后,服务端可以主动向前端推送消息。

STOMP(Simple Text Oriented Messaging Protocol,面向简单文本消息协议),是运行在WebSocket之上的应用层子协议,相当于一套消息通信规范,类似HTTP定义请求响应格式。原生WebSocket只提供字节流通道,没有消息路由、订阅机制;STOMP补齐了发布/订阅模型,消息依靠destination目的地进行路由,Spring框架对STOMP做了封装,也就是我们常说的 Spring WebSocket STOMP。

通俗类比:WebSocket相当于一根双向通话电话线;STOMP是电话双方约定好的通话指令规范,定义连接、订阅、发消息、断开、心跳等指令。

一、核心原理

  1. 握手阶段:客户端发起普通HTTP请求,请求头携带Upgrade: websocket标识。后端校验通过,返回101状态码,协议升级,TCP长连接正式建立。>

握手是HTTP,通信阶段是WebSocket长连接。

  1. STOMP帧通信:长连接通道内传输STOMP帧,每一帧代表一条指令。
  2. 消息代理Broker:Spring内置消息代理,接收消息,根据destination目的地,将消息分发到所有订阅该地址的客户端。

二、STOMP常用指令帧

指令作用
CONNECT客户端发起STOMP连接认证
SUBSCRIBE订阅某个目的地destination,监听推送消息
SEND客户端向指定目的地发送消息
MESSAGE服务端推送消息帧,发给订阅客户端
DISCONNECT客户端断开STOMP连接

三、目的地destination前缀含义(面试重点)

  1. /topic:广播主题,一对多。消息推送给所有订阅该topic的客户端,适合全局通知、群聊、大屏广播。
  2. /queue:点对点消息,一对一私聊、用户私有消息。
  3. /app:业务前缀。消息不会交给消息代理,转发给后端@MessageMapping控制器执行业务逻辑。
  4. /user:用户专属私有目的地,结合登录认证,消息只推送给指定登录用户。前端订阅地址格式:/user/queue/xxx

四、核心注解

  1. @EnableWebSocketMessageBroker:开启STOMP消息代理,放在SpringBoot启动类或者配置类,开启WebSocket STOMP功能,核心注解。
  2. @MessageMapping:接收客户端通过STOMP的SEND指令发送过来的消息,作用类比SpringMVC中的@RequestMapping。
    @RequestMapping匹配HTTP请求URL;@MessageMapping匹配STOMP消息的目的地destination。客户端发送消息到/app/xxx,配置里设置前缀/app,注解只需要填写后面路径/xxx,匹配成功就进入该方法执行业务。>

区别:@RequestMapping是HTTP短连接,一问一答;@MessageMapping基于WebSocket长连接,一次握手建立连接后,可以反复发送消息,多次触发该方法。

  1. @SendTo:方法处理业务完成后,自动将返回结果推送到指定topic,广播给所有订阅客户端。
  2. @SendToUser:消息只推送给当前登录认证用户,用于一对一私聊。

五、后端完整配置类

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; @Configuration @EnableWebSocketMessageBroker public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer { /** * 注册STOMP连接端点,前端连接地址:ws://localhost:8080/ws */ @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws") // 跨域配置,线上不要写*,指定前端域名 .setAllowedOriginPatterns("*") // SockJS降级方案 .withSockJS() // 设置心跳,单位毫秒,10秒心跳保活 .setHeartbeatTime(10000); } /** * 配置消息代理 */ @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 开启内置内存消息代理,处理topic、queue目的地 registry.enableSimpleBroker("/topic","/queue"); // 应用消息前缀,客户端发送业务消息以 /app 开头 registry.setApplicationDestinationPrefixes("/app"); } }

六、Controller业务代码示例

示例1:广播消息(群聊,@SendTo)

import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; @Controller public class StompChatController { // 前端发送消息目的地:/app/chat @MessageMapping("/chat") @SendTo("/topic/chatRoom") // 处理结果广播推送给所有订阅 /topic/chatRoom 的客户端 public String chatHandle(String msg){ return "收到广播消息:" + msg; } }

示例2:一对一私聊(@SendToUser)

@Controller public class StompPrivateMsgController { // 前端发送消息:/app/privateMsg @MessageMapping("/privateMsg") @SendToUser("/queue/private") // 仅推送给当前登录用户 public String privateMsgHandle(String message){ return "私聊消息内容:" + message; } }

前端订阅私聊地址:/user/queue/private

七、前端 stomp.js 完整示例

前端引入sockjs-client和stompjs,建立连接、订阅主题、发送消息。

<!-- 引入依赖js --> <script src="[https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js](https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js)"></script> <script src="[https://cdn.jsdelivr.net/npm/stompjs@6/dist/esm5/stomp.min.js](https://cdn.jsdelivr.net/npm/stompjs@6/dist/esm5/stomp.min.js)"></script> <script> // 1. 创建SockJS连接实例,后端端点/ws const socket = new SockJS('[http://localhost:8080/ws](http://localhost:8080/ws)'); // 2. 创建STOMP客户端 const stompClient = Stomp.over(socket); // 3. 发起STOMP连接 stompClient.connect({}, function(frame) { console.log('STOMP连接成功', frame); // 订阅广播主题 /topic/chatRoom stompClient.subscribe('/topic/chatRoom', function(msg) { console.log("收到广播消息:", msg.body); }); // 订阅个人私聊消息 /user/queue/private stompClient.subscribe('/user/queue/private', function(msg) { console.log("收到私聊消息:", msg.body); }); // 发送消息到后端业务接口 /app/chat stompClient.send("/app/chat", {}, "Hello STOMP!"); }, function(error) { // 连接失败回调 console.error("STOMP连接失败", error); }); </script>

八、SockJS是什么

部分老旧浏览器、防火墙、反向代理会拦截原生WebSocket协议。
.withSockJS()开启降级策略:优先尝试原生WebSocket;失败自动切换为http长轮询、xhr-stream。

优势:后端、前端业务代码完全不用改动,自动兼容低版本环境。

九、心跳机制 HeartBeat(生产环境必备)

长时间空闲连接,防火墙、Nginx会主动断开TCP连接,服务端无法感知客户端掉线,造成无效连接占用服务器资源。
心跳用于连接保活、检测客户端是否离线。
在注册端点时通过setHeartbeatTime(10000)设置心跳周期(单位ms,示例10s),服务端定时发送心跳包,客户端回复心跳,维持连接。

十、两种消息Broker对比

SimpleBroker(内置内存Broker)

SimpleBroker是Spring STOMP内置、基于JVM内存实现的简易消息代理,Broker即消息代理,负责接收消息、维护客户端订阅关系,并根据目的地destination完成消息分发。消息和订阅信息全部保存在当前JVM内存,不需要额外部署MQ中间件,开箱即用。

  • 优点:零额外组件依赖,开发测试快速验证功能,配置简单,轻量易用。
  • 缺点:仅单机有效,集群多实例之间内存隔离,消息无法跨服务转发;消息无持久化,服务重启订阅关系、消息全部丢失;缺少消息ACK、死信、消息重试等高级特性。
类型SimpleBroker(内置内存Broker)外部Broker RabbitMQ/ActiveMQ
部署内存实现,无需额外中间件,开箱即用需要独立部署MQ,并开启STOMP插件
集群支持❌ 单机有效,多实例之间消息隔离,A服务消息不能推送到B服务连接的客户端✅ 支持集群多实例,消息跨节点互通
适用场景单机项目、小流量演示、简单测试高并发、大量长连接、生产环境
消息持久化❌ 内存消息,服务重启消息丢失✅ 支持消息持久化、消息重试、死信队列
性能上限连接数量有限支持上万长连接,性能更强

开启方式:registry.enableSimpleBroker("/topic","/queue");

十一、踩坑点与注意事项

  1. 跨域问题
    前后端分离项目,前端与后端域名端口不一致,需要setAllowedOriginPatterns()配置跨域。生产环境不要直接写*,指定前端域名提升安全性。
  2. 认证鉴权
    STOMP身份校验在握手阶段完成。连接建立之后,普通SpringMVC拦截器不再生效,需要使用ChannelInterceptor拦截STOMP消息帧做权限校验。
  3. 集群部署大坑
    内置SimpleBroker存储在JVM内存,多台服务实例之间互不感知。集群环境必须接入RabbitMQ作为外部STOMP消息代理,才能实现跨服务器消息推送。
  4. 连接资源限制
    每一条WebSocket长连接占用一个TCP文件句柄。在线用户量大时,需要调整服务器操作系统最大文件句柄数,防止连接打满。
  5. Nginx反向代理配置
    Nginx代理WebSocket长连接,必须添加升级相关配置,否则握手失败、连接很快断开,需要配置Upgrade头、保持长连接超时时间。
  6. 消息丢失
    SimpleBroker消息保存在内存,服务重启消息全部丢失;需要消息持久化场景,使用RabbitMQ外部Broker。
  7. 什么是SimpleBroker
    SimpleBroker是Spring WebSocket STOMP内置的内存消息代理,在JVM内存维护客户端订阅关系,负责消息路由分发,无需额外MQ中间件。只能单机使用,集群环境下多台实例之间无法互通消息,消息不保持久化,适合开发测试;生产集群项目建议接入RabbitMQ外部Broker。

十二、适用场景

✅ 推荐场景:系统站内实时通知、在线聊天、数据大屏实时刷新、订单状态实时推送、物联网设备状态上报。
❌ 不推荐:超大规模百万级长连接场景(需要专业MQ架构优化)。

十三、面试精简背诵版

Spring WebSocket STOMP,是在WebSocket双向长连接基础上,使用STOMP子协议实现发布订阅模式的实时消息推送。WebSocket依靠HTTP握手升级TCP长连接,解决HTTP只能一问一答的局限;STOMP提供SUBSCRIBE订阅、SEND发送消息,通过destination目的地路由消息。
使用@EnableWebSocketMessageBroker开启功能,@MessageMapping接收客户端消息,@SendTo实现广播推送,@SendToUser实现一对一私聊。SimpleBroker是内存内置代理,仅支持单机;集群生产环境需要接入RabbitMQ作为外部STOMP Broker。搭配SockJS实现浏览器降级,心跳机制保活连接。多用于实时通知、在线聊天、实时数据大屏。

十四、高频面试题

  1. WebSocket 和 STOMP 的区别?
    WebSocket是底层双向长连接TCP协议;STOMP是运行在WebSocket之上的应用层文本子协议,定义消息帧、发布订阅、目的地路由。原生WebSocket没有订阅和消息路由能力,STOMP做了上层封装。
  2. /topic /queue /app /user 四个目的地前缀分别是什么?
  • /topic:广播,一对多,群发消息;
  • /queue:点对点消息;
  • /app:交给后端@MessageMapping执行业务逻辑;
  • /user:用户私有目的地,一对一推送私聊消息。
  1. SockJS的作用?
    浏览器、防火墙不支持原生WebSocket时,自动降级为HTTP长轮询,业务代码无需修改,兼容老旧环境。
  2. SimpleBroker为什么集群不能用?
    SimpleBroker消息存储在当前JVM内存,多台服务实例之间不共享消息,A服务收到消息,B服务上建立连接的客户端收不到广播,集群环境必须接入RabbitMQ。
  3. STOMP心跳的作用?
    TCP连接保活,识别掉线客户端,及时释放无效TCP连接,避免服务器连接资源泄漏。
  4. STOMP连接认证为什么不能用普通MVC拦截器?
    WebSocket握手完成后,通信走消息通道,不再经过SpringMVC的Servlet链路,MVC拦截器失效,要使用ChannelInterceptor拦截消息帧。
  5. @MessageMapping和@RequestMapping的区别?
    @RequestMapping处理HTTP请求,匹配URL,属于短连接,一问一答;@MessageMapping接收STOMP的SEND消息,匹配destination目的地,基于WebSocket长连接,一次握手建立连接后可以反复收发消息。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询