WebSocket 详解 + 高级实战用法(Spring Boot + Java)

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

一、为什么需要 WebSocket?

传统的 Web 应用基于 HTTP 协议,是请求-响应模式:

  • 客户端发请求 → 服务端处理 → 返回结果 → 连接关闭
  • 如果服务端有新数据(如聊天消息、订单状态变更),无法主动推给客户端

为实现“实时通信”,早期常用 轮询(Polling)长轮询(Long Polling)

  • 轮询:每隔几秒发一次请求 → 浪费带宽、延迟高
  • 长轮询:请求挂起直到有数据 → 仍基于 HTTP,连接频繁建立/断开

WebSocket 的优势

  • 全双工通信:客户端和服务端可随时互相发送数据
  • 单 TCP 连接:握手后复用连接,低开销
  • 低延迟、高效率:适合实时场景(聊天、股票、游戏、监控)

二、WebSocket 协议简析

  1. 连接建立:后续通信使用 WebSocket 帧(Frame),不再是 HTTP

服务端响应

HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= 

握手阶段:客户端通过 HTTP 发起 Upgrade 请求

GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 
🔑 关键:一次握手,长期通信

三、Spring Boot 整合 WebSocket(基础版)

场景:简易在线聊天室

1. 添加依赖(pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> 
2. 启用 WebSocket 支持
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 注册 STOMP 端点,允许跨域 registry.addEndpoint("/ws") .setAllowedOriginPatterns("*") .withSockJS(); // 兼容不支持 WebSocket 的浏览器(降级到 xhr-streaming) } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 客户端订阅前缀 registry.enableSimpleBroker("/topic", "/queue"); // 服务端推送前缀 registry.setApplicationDestinationPrefixes("/app"); // 客户端发送消息前缀 } } 
💡 使用 STOMP(Simple Text Oriented Messaging Protocol)作为子协议,简化消息路由。
3. 编写控制器(处理客户端消息)
@Controller public class ChatController { @MessageMapping("/chat.sendMessage") // 客户端发送到 /app/chat.sendMessage @SendTo("/topic/public") // 广播给所有订阅 /topic/public 的客户端 public ChatMessage sendMessage(ChatMessage chatMessage) { return chatMessage; } @MessageMapping("/chat.addUser") @SendTo("/topic/public") public ChatMessage addUser(@Payload ChatMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) { // 将用户名加入会话 headerAccessor.getSessionAttributes().put("username", chatMessage.getSender()); chatMessage.setContent("加入聊天室"); return chatMessage; } } 
4. 定义消息实体
@Data @NoArgsConstructor @AllArgsConstructor public class ChatMessage { private String content; private String sender; private MessageType type; // enum: CHAT, JOIN, LEAVE public enum MessageType { CHAT, JOIN, LEAVE } } 
5. 前端连接(HTML + JS)
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/stomp.min.js"></script> <script> const stompClient = Stomp.over(new SockJS('/ws')); stompClient.connect({}, () => { console.log('连接成功'); // 订阅公共频道 stompClient.subscribe('/topic/public', (payload) => { const msg = JSON.parse(payload.body); document.getElementById('chat').innerHTML += `<div><b>${msg.sender}</b>: ${msg.content}</div>`; }); // 发送消息 function sendMessage() { const content = document.getElementById('message').value; stompClient.send("/app/chat.sendMessage", {}, JSON.stringify({ content: content, sender: '张三', type: 'CHAT' })); } }); </script> 

效果:用户加入、发消息,所有在线用户实时看到。


四、高级实战用法

场景1:私聊(点对点通信)

❌ 反例:用 /topic 广播实现私聊 → 信息泄露!
✅ 正确做法:使用 用户专属队列
// 获取当前登录用户(需集成 Spring Security) private String getCurrentUser(SimpMessageHeaderAccessor headerAccessor) { // 从 token / session 中解析 return (String) headerAccessor.getSessionAttributes().get("username"); } @MessageMapping("/chat.private") public void sendPrivateMessage(@Payload ChatMessage message, SimpMessageHeaderAccessor headerAccessor) { String currentUser = getCurrentUser(headerAccessor); String recipient = message.getRecipient(); // 假设消息中包含接收者 // 构造用户专属队列路径 String destination = "/queue/messages/" + recipient; // 使用 convertAndSendToUser 发送(自动加 /user 前缀) messagingTemplate.convertAndSendToUser(recipient, "/queue/messages", message); } 
⚠️ 注意:前端需订阅 /user/queue/messages(STOMP 会自动映射到用户会话)
// 前端订阅私信 stompClient.subscribe('/user/queue/messages', (payload) => { const msg = JSON.parse(payload.body); // 显示私聊消息 }); 

场景2:连接管理 & 在线人数统计

@Component public class WebSocketEventListener { private static final Set<String> onlineUsers = ConcurrentHashMap.newKeySet(); @EventListener public void handleWebSocketConnectListener(SessionConnectedEvent event) { // 用户上线(需结合认证) System.out.println("用户连接: " + event.getMessage().getHeaders()); } @EventListener public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) { StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); String username = (String) headerAccessor.getSessionAttributes().get("username"); if (username != null) { onlineUsers.remove(username); // 广播在线人数 messagingTemplate.convertAndSend("/topic/online", onlineUsers.size()); } } } 
可用于“好友在线状态”、“客服排队人数”等场景。

场景3:消息持久化 & 离线消息

当用户离线时,消息不能丢!

方案:
  1. 发送前检查用户是否在线(维护 Map<userId, sessionId>
  2. 若不在线,将消息存入数据库(offline_messages 表)
  3. 用户上线时,拉取离线消息并推送
public void sendPrivateMessage(ChatMessage message) { if (isUserOnline(message.getRecipient())) { messagingTemplate.convertAndSendToUser(...); } else { offlineMessageService.save(message); // 存 DB } } // 用户上线时 @EventListener public void onUserOnline(SessionConnectedEvent event) { String userId = getCurrentUserId(event); List<ChatMessage> offlineMsgs = offlineMessageService.getAndDelete(userId); offlineMsgs.forEach(msg -> messagingTemplate.convertAndSendToUser(userId, "/queue/offline", msg) ); } 

五、注意事项 & 常见坑点

问题解决方案
跨域问题setAllowedOriginPatterns("*")(生产环境应限制域名)
集群部署单机内存存储会话 → 多节点无法互通 → 需用 Redis + WebSocket 消息广播
连接断开重连前端监听 onclose,自动重连 + 重新订阅
消息丢失关键消息需 ACK 机制 + 重试(WebSocket 本身不保证可靠)
安全认证握手时校验 Token(通过 HandshakeInterceptor
示例:Token 认证拦截器
public class HttpHandshakeInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) { if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; String token = servletRequest.getServletRequest().getParameter("token"); if (isValidToken(token)) { attributes.put("userId", parseUserId(token)); return true; } } return false; // 拒绝连接 } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {} } 

注册到配置中:

registry.addEndpoint("/ws") .addInterceptors(new HttpHandshakeInterceptor()) .setAllowedOriginPatterns("*"); 

六、替代方案对比

技术优点缺点适用场景
WebSocket真正双向、低延迟需要保持长连接聊天、实时协作、游戏
SSE (Server-Sent Events)简单、基于 HTTP仅服务端→客户端实时通知、日志流
MQTT over WebSocket轻量、支持 QoS需要额外 BrokerIoT 设备通信
gRPC-Web高性能、强类型浏览器支持有限内部微服务调用

七、总结

WebSocket 是构建实时 Web 应用的核心技术,关键要点:

  • 使用 STOMP + Spring Message Broker 简化开发
  • 私聊用 /user/queue,广播用 /topic
  • 连接状态管理 实现在线人数、离线消息
  • 集群部署需 Redis 广播(如 spring-boot-starter-redis + RedisMessageListener
  • 安全第一:握手时校验身份,避免未授权连接
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

Read more

【亮数据 × Dify】零代码秒搭 AI 实时爬虫,数据伸手就来!

【亮数据 × Dify】零代码秒搭 AI 实时爬虫,数据伸手就来!

主要演示了如何用亮数据(Bright Data)+ Dify 零代码搭建一个 AI 实时爬虫工具,实现自动抓取网页数据并生成分析报告。核心流程如下: ✅ 总结: 1. 工具介绍 * Dify:开源的大语言模型应用开发平台,支持无代码搭建 AI 应用。 * 亮数据(Bright Data):提供网页抓取服务,支持 API 调用。 2. 操作步骤 步骤内容1. 登录 Dify 云需科学上网,支持 GitHub / 谷歌 / 邮箱登录。2. 安装插件在 Dify 插件市场通过 GitHub 链接安装“亮数据”插件。3. 创建应用新建空白应用 → 选择“工作流”模式 → 命名项目。4. 配置工作流构建

使用 VS Code 连接 MySQL 数据库

使用 VS Code 连接 MySQL 数据库

文章目录 * 前言 * VS Code下载安装 * 如何在VS Code上连接MySQL数据库 * 1、打开扩展 * 2、安装MySQL插件 * 3、连接 * 导入和导出表结构和数据 前言 提示:这里可以添加本文要记录的大概内容: 听说VS Code不要钱,功能还和 Navicat 差不多,还能在上面打游戏 但是没安装插件是不行的 发现一个非常牛的博主 还有一个非常牛的大佬 提示:以下是本篇文章正文内容,下面案例可供参考 VS Code下载安装 VS Code下载安装 如何在VS Code上连接MySQL数据库 本篇分享是在已有VS Code这个软件的基础上,数据库举的例子是MySQL 1、打开扩展 2、安装MySQL插件 在搜索框搜索 MySQL和 MySQL Syntax,下载这三个插件 点击下面的插件,选择【install】安装

阿里云全品类 8 折券限时领,建站 / AI / 存储通用 立即领取