WebSocket 在 Spring Boot 中的实战解析:实时通信的技术利器

作者:苏渡苇日期:2026/1/15

WebSocket 在 Spring Boot 中的实战解析:实时通信的技术利器

一、引言:为什么我们需要 WebSocket?

在传统的 Web 应用中,客户端(浏览器)与服务器之间的通信是 请求-响应 模式:客户端发起请求,服务器处理后返回结果。这种模式适用于大多数场景,但在需要 实时双向通信 的场景下(如聊天室、股票行情、在线协作、游戏等),频繁轮询(Polling)或长轮询(Long Polling)会带来高延迟、高开销的问题。

WebSocket 协议应运而生——它提供了一种全双工、低延迟、持久化的通信通道,允许服务器主动向客户端推送数据,彻底改变了 Web 实时交互的格局。

在 Spring Boot 生态中,通过 spring-boot-starter-websocket 模块,我们可以轻松集成 WebSocket,并结合 STOMP(Simple Text-Oriented Messaging Protocol)实现更高级的消息路由与订阅机制。

1.1 从“轮询”到“长连接”的进化史

在WebSocket出现之前,实现实时通信主要靠这些“土办法”:

技术方案工作原理缺点
短轮询客户端每隔几秒问一次:“有新消息吗?”浪费带宽,实时性差
长轮询客户端问“有新消息吗?”,服务器hold住,有消息才回复连接占用时间长,服务器压力大
SSE服务器单向推送,客户端只能接收单向通信,功能有限

WebSocket的登场改变了游戏规则:

  • 一次握手,持久连接:建立连接后,双向通道一直打开
  • 服务端主动推送:服务器想什么时候发就什么时候发
  • 极低的通信开销:没有HTTP头部的重复传输

1.2 WebSocket vs HTTP:本质区别

HTTP交互流程(像发短信):

HTTP交互

每次请求都要重复:建立TCP连接 → TLS握手 → 发送HTTP头部 → 传输数据 → 断开连接

WebSocket交互流程(像打电话):

WebSocket

二者的关键区别:

局部截取_20260116_002240.png

二、Spring Boot 中的 WebSocket 技术栈

Spring 对 WebSocket 的支持分为两个层次:

层级技术说明
底层javax.websocket 或 Spring 原生 WebSocket直接处理原始 WebSocket 消息
高层STOMP over WebSocket基于消息代理的发布/订阅模型,更易开发

推荐使用 STOMP:它提供了类似 JMS 的语义(目的地、订阅、广播),适合复杂业务场景。

2.1 核心注解与类

组件作用
@EnableWebSocketMessageBroker启用 WebSocket 消息代理
WebSocketMessageBrokerConfigurer配置 STOMP 端点与消息代理
@MessageMapping映射客户端发送到特定路径的消息
SimpMessagingTemplate服务端主动向客户端推送消息

2.2 三步快速集成

第一步:添加依赖

1<dependency>
2    <groupId>org.springframework.boot</groupId>
3    <artifactId>spring-boot-starter-websocket</artifactId>
4</dependency>
5

第二步:配置WebSocket

1@Configuration
2@EnableWebSocket
3public class WebSocketConfig implements WebSocketConfigurer {
4    
5    @Override
6    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
7        // 注册处理器,指定连接路径
8        registry.addHandler(myWebSocketHandler(), "/ws")
9                .setAllowedOrigins("*")  // 生产环境要限制具体域名
10                .withSockJS();  // 为不支持WebSocket的浏览器提供降级方案
11    }
12    
13    @Bean
14    public WebSocketHandler myWebSocketHandler() {
15        return new MyWebSocketHandler();
16    }
17}
18

第三步:实现核心处理器

1@Component
2public class MyWebSocketHandler extends TextWebSocketHandler {
3    
4    // 保存所有活跃连接
5    private static final Map<String, WebSocketSession> sessions = 
6        new ConcurrentHashMap<>();
7    
8    @Override
9    public void afterConnectionEstablished(WebSocketSession session) {
10        // 连接建立时调用
11        String userId = extractUserId(session);
12        sessions.put(userId, session);
13        log.info("用户 {} 连接成功,当前在线: {} 人", userId, sessions.size());
14        
15        // 发送欢迎消息
16        session.sendMessage(new TextMessage("连接成功!"));
17    }
18    
19    @Override
20    protected void handleTextMessage(WebSocketSession session, 
21                                     TextMessage message) {
22        // 处理客户端发送的消息
23        String payload = message.getPayload();
24        log.info("收到消息: {}", payload);
25        
26        // 处理业务逻辑,比如广播或定向回复
27        handleMessage(session, payload);
28    }
29    
30    @Override
31    public void afterConnectionClosed(WebSocketSession session, 
32                                      CloseStatus status) {
33        // 连接关闭时调用
34        String userId = extractUserId(session);
35        sessions.remove(userId);
36        log.info("用户 {} 断开连接,原因: {}", userId, status);
37    }
38}
39

2.3 进阶:使用STOMP协议

2.3.1 什么是STOMP?

STOMP(Simple Text Oriented Messaging Protocol)是一个简单的文本协议,它为WebSocket提供了更高级的消息模式。如果说原始的WebSocket是"裸奔",那么STOMP就是给它穿上了"协议的外衣"。

STOMP的核心概念:

  • Destination(目的地):消息发送的目标地址
  • SUBSCRIBE(订阅):客户端订阅某个目的地
  • SEND(发送):客户端向目的地发送消息
  • MESSAGE(消息):服务器向客户端推送消息
2.3.2 STOMP协议结构

一个简单的STOMP帧示例:

1SEND
2destination:/app/chat
3content-type:application/json
4content-length:23
5
6{"text":"Hello World!"}
7

响应帧:

1MESSAGE
2destination:/topic/chat
3content-type:application/json
4content-length:45
5subscription:sub-0
6message-id:msg-123
7
8{"user":"Tom","text":"Hello World!"}
9
2.3.3 SpringBoot中的STOMP配置
1@Configuration
2@EnableWebSocketMessageBroker  // 启用STOMP消息代理
3public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
4    
5    @Override
6    public void registerStompEndpoints(StompEndpointRegistry registry) {
7        // 注册STOMP端点
8        registry.addEndpoint("/ws-stomp")
9                .setAllowedOrigins("*")
10                .withSockJS();  // SockJS支持
11    }
12    
13    @Override
14    public void configureMessageBroker(MessageBrokerRegistry registry) {
15        // 配置消息代理
16        registry.enableSimpleBroker("/topic", "/queue");  // 客户端订阅的前缀
17        registry.setApplicationDestinationPrefixes("/app");  // 客户端发送消息的前缀
18        registry.setUserDestinationPrefix("/user");  // 用户私信前缀
19    }
20}
21
2.3.4 STOMP控制器示例
1@Controller
2public class ChatController {
3    
4    // 处理发送到/app/chat的消息
5    @MessageMapping("/chat")
6    @SendTo("/topic/chat")  // 广播给所有订阅/topic/chat的客户端
7    public ChatMessage handleChatMessage(ChatMessage message) {
8        message.setTimestamp(LocalDateTime.now());
9        log.info("收到聊天消息: {}", message);
10        return message;
11    }
12    
13    // 处理私信
14    @MessageMapping("/private")
15    public void sendPrivateMessage(@Payload PrivateMessage message,
16                                   SimpMessageHeaderAccessor headerAccessor) {
17        // 获取发送者
18        String sender = headerAccessor.getUser().getName();
19        
20        // 使用convertAndSendToUser发送给特定用户
21        simpMessagingTemplate.convertAndSendToUser(
22            message.getRecipient(),  // 接收者用户名
23            "/queue/private",        // 用户私信队列
24            new PrivateMessage(sender, message.getRecipient(), message.getContent())
25        );
26    }
27    
28    // 处理订阅通知
29    @EventListener
30    public void handleSessionSubscribe(SessionSubscribeEvent event) {
31        StompHeaderAccessor headers = StompHeaderAccessor.wrap(event.getMessage());
32        String destination = headers.getDestination();
33        String sessionId = headers.getSessionId();
34        
35        log.info("Session {} 订阅了 {}", sessionId, destination);
36    }
37}
38
2.4.5 前端STOMP客户端示例
1// 连接STOMP服务器
2const socket = new SockJS('/ws-stomp');
3const stompClient = Stomp.over(socket);
4
5// 连接成功回调
6stompClient.connect({}, function(frame) {
7    console.log('Connected: ' + frame);
8    
9    // 订阅公共聊天频道
10    stompClient.subscribe('/topic/chat', function(message) {
11        const chatMsg = JSON.parse(message.body);
12        showMessage(chatMsg);
13    });
14    
15    // 订阅个人私信队列
16    stompClient.subscribe('/user/queue/private', function(message) {
17        const privateMsg = JSON.parse(message.body);
18        showPrivateMessage(privateMsg);
19    });
20});
21
22// 发送聊天消息
23function sendMessage() {
24    const message = {
25        content: document.getElementById('message').value,
26        sender: currentUser
27    };
28    stompClient.send("/app/chat", {}, JSON.stringify(message));
29}
30
31// 发送私信
32function sendPrivateMessage(toUser, content) {
33    const message = {
34        recipient: toUser,
35        content: content
36    };
37    stompClient.send("/app/private", {}, JSON.stringify(message));
38}
39

三、WebSocket的核心技术要点

3.1 连接生命周期管理

连接生命周期包括四个关键阶段,每个阶段需要处理不同的业务逻辑:

建立连接时(afterConnectionEstablished)需要处理:
  1. 验证用户身份 - 检查token或session,确保连接合法性
  2. 初始化会话状态 - 创建用户会话上下文,保存必要信息
  3. 通知相关服务用户上线 - 更新用户在线状态,通知好友
  4. 发送未读消息 - 推送离线期间积累的消息
1@Component
2public class WebSocketLifecycleManager {
3    
4    public void onOpen(String sessionId, String userId) {
5        // 1. 验证用户身份
6        if (!userService.validateToken(userId, getToken(sessionId))) {
7            closeConnection(sessionId, CloseStatus.NOT_ACCEPTABLE);
8            return;
9        }
10        
11        // 2. 初始化会话状态
12        SessionContext context = new SessionContext(userId, sessionId);
13        sessionStore.save(sessionId, context);
14        
15        // 3. 通知相关服务用户上线
16        presenceService.userOnline(userId);
17        notifyFriends(userId, true); // 通知好友用户上线
18        
19        // 4. 发送未读消息
20        List<Message> unreadMessages = messageService.getUnreadMessages(userId);
21        unreadMessages.forEach(msg -> sendMessage(sessionId, msg));
22    }
23}
24
消息处理时(handleTextMessage)需要处理:
  1. 消息格式验证 - 检查JSON格式、必要字段
  2. 业务逻辑处理 - 根据消息类型执行不同业务
  3. 消息持久化 - 保存到数据库,确保不丢失
  4. 响应或转发 - 回复发送者或转发给其他用户
1public void onMessage(String sessionId, String rawMessage) {
2    // 1. 消息格式验证
3    Message message;
4    try {
5        message = jsonMapper.readValue(rawMessage, Message.class);
6        validateMessage(message);
7    } catch (Exception e) {
8        sendError(sessionId, "消息格式错误");
9        return;
10    }
11    
12    // 2. 业务逻辑处理
13    switch (message.getType()) {
14        case "CHAT":
15            handleChatMessage(sessionId, message);
16            break;
17        case "COMMAND":
18            handleCommand(sessionId, message);
19            break;
20        // ... 其他消息类型
21    }
22    
23    // 3. 消息持久化
24    messageService.saveMessage(message);
25    
26    // 4. 响应或转发
27    if (message.needResponse()) {
28        sendResponse(sessionId, createResponse(message));
29    }
30    if (message.needForward()) {
31        forwardMessage(message.getTarget(), message);
32    }
33}
34
连接关闭时(afterConnectionClosed)需要处理:
  1. 清理会话资源 - 释放内存,关闭相关资源
  2. 更新用户状态为离线 - 标记用户下线时间
  3. 记录断开原因 - 用于分析连接稳定性
  4. 通知相关服务 - 通知好友用户下线
1public void onClose(String sessionId, CloseStatus status) {
2    // 1. 清理会话资源
3    SessionContext context = sessionStore.remove(sessionId);
4    if (context != null) {
5        context.cleanup();
6    }
7    
8    // 2. 更新用户状态为离线
9    String userId = getUserIdFromSession(sessionId);
10    presenceService.userOffline(userId);
11    
12    // 3. 记录断开原因
13    connectionLogService.logDisconnect(sessionId, userId, status.getCode(), status.getReason());
14    
15    // 4. 通知相关服务
16    notifyFriends(userId, false); // 通知好友用户下线
17    cleanupUserSubscriptions(userId); // 清理用户的所有订阅
18}
19
错误时(handleTransportError)需要处理:
  1. 记录错误日志 - 详细记录异常信息
  2. 尝试恢复连接 - 对于可恢复错误尝试重连
  3. 通知监控系统 - 触发告警,人工干预
  4. 优雅降级 - 切换到备用通信方式
1public void onError(String sessionId, Throwable error) {
2    // 1. 记录错误日志
3    log.error("WebSocket连接错误 sessionId: {}", sessionId, error);
4    
5    // 2. 尝试恢复连接(如果是网络波动等临时错误)
6    if (isRecoverableError(error)) {
7        scheduleReconnection(sessionId);
8    } else {
9        // 3. 通知监控系统
10        alertService.sendAlert("WebSocket连接异常", 
11            "Session: " + sessionId + ", Error: " + error.getMessage());
12        
13        // 4. 优雅降级
14        fallbackToHttp(sessionId, getUserIdFromSession(sessionId));
15        closeConnection(sessionId, CloseStatus.SERVER_ERROR);
16    }
17}
18

3.2 心跳机制与健康检查

1@Configuration
2public class HeartbeatConfig {
3    
4    @Bean
5    public ServletServerContainerFactoryBean createWebSocketContainer() {
6        ServletServerContainerFactoryBean container = 
7            new ServletServerContainerFactoryBean();
8        
9        // 重要配置
10        container.setMaxSessionIdleTimeout(300000L);     // 5分钟无活动断开
11        container.setMaxTextMessageBufferSize(8192);     // 最大消息大小
12        container.setMaxBinaryMessageBufferSize(8192);
13        container.setAsyncSendTimeout(5000L);           // 异步发送超时
14        
15        return container;
16    }
17}
18
19// 心跳检测实现
20@Component
21public class HeartbeatService {
22    
23    private final ScheduledExecutorService scheduler = 
24        Executors.newScheduledThreadPool(1);
25    
26    @PostConstruct
27    public void startHeartbeat() {
28        scheduler.scheduleAtFixedRate(() -> {
29            checkConnections();
30            sendPing();
31        }, 30, 30, TimeUnit.SECONDS);  // 每30秒检测一次
32    }
33    
34    private void checkConnections() {
35        // 检查所有连接的健康状态
36        // 移除僵尸连接
37        // 记录连接统计信息
38    }
39    
40    private void sendPing() {
41        // 向所有活跃连接发送ping消息
42        // 处理未响应pong的连接
43    }
44}
45

3.3 消息可靠性与重连机制

1public class ReliableMessageService {
2    
3    // 消息确认机制
4    public void sendWithAck(String sessionId, String message) {
5        String msgId = generateMsgId();
6        
7        // 发送消息
8        webSocketHandler.send(sessionId, wrapMessage(msgId, message));
9        
10        // 启动确认计时器
11        scheduler.schedule(() -> {
12            if (!isAcked(msgId)) {
13                log.warn("消息 {} 未确认,尝试重发", msgId);
14                retrySend(sessionId, msgId, message);
15            }
16        }, 5, TimeUnit.SECONDS);
17    }
18    
19    // 客户端重连处理
20    public void handleReconnect(String oldSessionId, String newSessionId) {
21        // 1. 转移会话状态
22        // 2. 重发未确认消息
23        // 3. 恢复订阅关系
24        // 4. 更新会话映射
25    }
26    
27    // 消息去重
28    public boolean isDuplicate(String msgId) {
29        // 基于Redis或本地缓存实现
30        // 防止重复处理消息
31        return false;
32    }
33}
34

四、实战:写一个监控SpringBoot应用的实时监控告警系统

Spring Insight 是我的一个开源项目,目前正在紧张的开发中。项目地址:github.com/iweidujiang…,欢迎关注,顺便求个 star,哈哈。

求求你在线编辑-求关注公众号表情包配图-图司机

在监控诊断类工具中,WebSocket 可以:

  1. 实时告警:第一时间发现问题
  2. 动态拓扑:实时展示微服务依赖变化
  3. 性能监控:实时推送指标数据
  4. 在线诊断:实时查看日志和跟踪信息

例,实时统计当前监控信息:

1/**
2 * 广播实时统计信息(每5秒一次)
3 */
4@Scheduled(fixedDelay = 5000)
5public void broadcastStats() {
6    if (connectionCount.get() == 0) return;
7
8    try {
9        // 获取最新数据
10        var collectorStats = dataCollectorService.getCollectorStats();
11        var serviceStats = dataCollectorService.getServiceStats();
12        var errorAnalysis = dataCollectorService.getErrorAnalysis(1); // 最近1小时
13
14        // 构建消息
15        Map<String, Object> data = new HashMap<>();
16        data.put("collectorStats", collectorStats);
17        data.put("serviceStats", serviceStats.subList(0, Math.min(5, serviceStats.size())));
18        data.put("errorAnalysis", errorAnalysis.subList(0, Math.min(5, errorAnalysis.size())));
19        data.put("timestamp", Instant.now().toString());
20        data.put("cacheSize", dataCollectorService.getCacheSize());
21
22        WebSocketMessage message = new WebSocketMessage();
23        message.setType("STATS_UPDATE");
24        message.setData(data);
25
26        // 广播消息
27        messagingTemplate.convertAndSend("/topic/stats", message);
28        log.debug("广播实时统计信息");
29
30    } catch (Exception e) {
31        log.error("广播实时统计信息失败", e);
32    }
33}
34

五、其他典型使用场景

场景说明WebSocket 优势
在线客服/聊天系统用户与客服实时对话低延迟、支持多房间
股票/金融行情推送实时价格更新减少服务器压力,避免轮询
协同编辑多人同时编辑文档实时同步操作,冲突检测
游戏状态同步多人在线小游戏高频消息传递,毫秒级响应
IoT 设备监控传感器数据上报长连接节省资源

✅ 用WebSocket:

  • 实时双向通信需求(聊天、协作)
  • 高频数据推送(监控、行情)
  • 低延迟要求(游戏、实时控制)
  • 服务端主动通知(告警、状态更新)

❌ 不用WebSocket:

  • 简单的请求-响应模式(REST API足够)
  • 客户端偶尔拉取数据(用HTTP轮询)
  • 单向信息流(考虑SSE)
  • 移动端弱网络环境(可能连接不稳定)

六、总结

WebSocket 是构建现代实时 Web 应用的基石。Spring Boot 通过简洁的配置和强大的 STOMP 支持,让开发者能够快速实现高性能、可扩展的双向通信系统。

记住:不是所有场景都需要 WebSocket。对于低频更新(如每分钟一次),传统 REST + 定时轮询可能更简单。但在高频、低延迟、事件驱动的场景下,WebSocket 几乎是唯一选择。


我的技术思考和实践,统一沉淀在这些地方:

🌍 微信公众号:「苏渡苇」

推送最稳定,适合深度阅读

⭐️ 掘金:「苏渡苇」

juejin.cn/user/729731…

💡 知乎:「苏渡苇」

www.zhihu.com/people/iwei…

📘 CSDN:「苏渡苇」

blog.csdn.net/iweidujiang

🐙 GitHub:github.com/iweidujiang

所有代码的源头,包括 Spring Insight 开源项目,感谢 Star ⭐

非常感谢你关注我。


WebSocket 在 Spring Boot 中的实战解析:实时通信的技术利器》 是转载文章,点击查看原文


相关推荐


小迪安全第二十六天
江边鸟2192026/1/6

写好这些配置好相应的数据库内容 发现不足套用模板使用模板框架 <!DOCTYPE html> <html> <head>    <meta charset="UTF-8">    <!-- 页面标题(动态变量) -->    <title>{page_title}</title>    <style>        /* 全局样式 */        body {            font-family: Arial, sans-serif;  /* 设置默认字体


激活函数有什么用?有哪些常用的激活函数?
aicoting2025/12/29

在深度学习中,激活函数(Activation Function)是神经网络的灵魂。它不仅赋予网络非线性能力,还决定了训练的稳定性和模型性能。那么,激活函数到底是什么?为什么我们非用不可?有哪些经典函数?又该如何选择?本文带你全面解析。 所有相关源码示例、流程图、面试八股、模型配置与知识库构建技巧,我也将持续更新在Github:AIHub,欢迎关注收藏! 阅读本文时,请带着这三个问题思考: 什么是激活函数,为什么需要激活函数? 经典的激活函数有哪些? 怎么选择激活函数? 1. 什么是激活函数,


别再死磕扩散模型了,MiniMax新开源揭示:视觉Tokenizer才是下一个金矿
墨风如雪2025/12/20

在AI绘画和视频生成卷到飞起的今天,不管是大厂还是开源社区,大家似乎都陷入了一个怪圈:拼命堆算力去训练更大的Diffusion Transformer(DiT),指望通过增加生成模型的参数来获得更好的画质。 但就在前两天,凭借海螺视频(Hailuo AI)在圈内名声大噪的MiniMax团队,突然开源了一个名为VTP(Visual Tokenizer Pre-training)的项目。看完他们的论文和代码,我不得不说,这帮人可能刚刚掀翻了视觉生成领域的桌子。 他们抛出了一个极其反直觉的结论:如果我


【转载】我们在大型开源项目上对 7 个 AI 代码审查工具进行了基准测试。以下是结果。
是魔丸啊2025/12/12

转载 2025年12月11日 TL;DR 我们在唯一的 AI 辅助代码审查公共基准测试上评估了七个最广泛使用的 AI 代码审查工具。Augment Code Review,由 GPT-5.2 驱动,以显著优势交付了最强的性能。它的审查既具有更高的精确度,又具有大幅更高的召回率,这得益于其独特强大的 Context Engine,能够持续检索正确的文件、依赖项和调用站点。虽然许多工具由于有限的上下文而产生嘈杂的评论或遗漏关键问题,但 Augment 作为唯一能够可靠地在整个代码库中进行推理并发现


Bun 卖身 Anthropic!尤雨溪神吐槽:OpenAI 你需要工具链吗?
也无风雨也雾晴2025/12/3

Anthropic 收购 Bun,Claude Code 半年营收破 10 亿美元 今天刷推的时候看到一条爆炸新闻:Anthropic 把 Bun 给收了。 是的,就是那个号称"比 Node.js 快得多"的 JavaScript 运行时。这也是 Anthropic 成立以来的第一笔收购。 更劲爆的是,官宣的同时还顺便秀了一把肌肉——Claude Code 上线半年,年化收入已经突破 10 亿美元。 网友速度很快,恶搞图已经出来了:Bun 屁股上印着 Claude 的 logo 先说说 Cla


Rust 所有权与借用:从堆栈开始建立心智模型
mCell2026/1/23

本文写作时,极大的借鉴了《The Rust Programming Language》(俗称“Rust 圣经”)中相关章节的内容和结构,在此表示感谢。 写 Rust 的第一道坎,不是语法,也不是宏,而是“我明明只是把变量传给你用一下,怎么它就不属于我了?” 这类困惑通常并不奇怪,因为我们习惯了别的语言那套“内存默认有人兜底”的模型,比如 Javascript、Golang 的自动垃圾回收机制。Rust 恰恰相反:它要求你把内存这件事想清楚,然后把规则写进类型系统,交给编译器在编译期强制执行——

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客