Spring WebSocket(STOMP)

Spring WebSocket(STOMP)

一句话定义

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长连接,一次握手建立连接后可以反复收发消息。

相关推荐
Zhao-Jichao2 小时前
AI Prompting for Everyone 课程笔记 - P21 总结 Conclusion
人工智能·笔记
欢欢李2 小时前
【LTE L3 笔记 05】LTE同步信号
笔记·无线通信·物理层·lte·空口协议
La Pulga4 小时前
【FreeRTOS源码学习笔记】空闲任务与阻塞延时
笔记·mcu·学习·嵌入式·freertos·实时操作系统
蒸蒸yyyyzwd4 小时前
秋招学习笔记 day106
笔记·学习
小李不想当小白5 小时前
WDG看门狗(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
欢欢李6 小时前
【LTE L3 笔记 04】LTE帧结构
笔记·无线通信·物理层·lte·空口协议
Zhao-Jichao8 小时前
AI Prompting for Everyone 课程笔记 - P20 实验与期末项目 Lab and Final Project
前端·人工智能·笔记
Z21-X8 小时前
云原生运维实战笔记:企业网站综合上云部署与云运维服务全景解析
运维·笔记·学习·云原生
坤坤子吖9 小时前
Linux网络编程:UDP进阶,从字典服务器到简单聊天室
linux·服务器·网络·c++·笔记·学习
Rabitebla9 小时前
【Linux学习笔记】进程篇(九):进程的创建与退出——fork、写时拷贝与退出码
linux·c++·笔记·学习