Spring WebSocket实战:实时通信全解析
🌐 一、为什么要用 WebSocket?HTTP 不够吗?
❌ 传统 HTTP 的局限
- 请求-响应模式:客户端发请求 → 服务器返回结果 → 连接关闭。
- 无法实现“服务器主动推送”数据给客户端。
- 比如:聊天室、股票行情、实时协作编辑……如果靠浏览器每隔几秒轮询一次服务器,效率极低,延迟高。
✅ WebSocket 的优势
- 建立一次连接后,全双工、双向通信。
- 客户端和服务器都可以随时发送消息。
- 基于 TCP,但通过标准端口(80/443),兼容现有防火墙。
- 初始握手是 HTTP 协议,然后“升级”为 WebSocket 协议。
GET /chat HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
服务器回应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
✅ 握手成功!之后双方就可以通过这个持久连接互相发消息了。
🔗 二、WebSocket vs HTTP 架构对比
| 特性 | HTTP / REST | WebSocket | 
|---|---|---|
| 通信方式 | 请求-响应 | 全双工、事件驱动 | 
| 连接数 | 每次请求新建或复用短连接 | 长连接,持续通信 | 
| 路由机制 | URL + Method (GET/POST) | 没有内置路由,需自定义协议 | 
| 数据语义 | 明确(JSON/XML等) | 自定义格式(文本/二进制) | 
💡 所以:WebSocket 是底层传输协议,不规定消息含义。你需要自己定义“消息长什么样”。
🧩 三、什么时候该用 WebSocket?
适合以下场景:
| 场景 | 是否推荐 | 
|---|---|
| 新闻刷新 | ❌ 可用 Ajax 轮询或 SSE | 
| 实时聊天 | ✅ 必须用 WebSocket | 
| 在线协作文档 | ✅ 强依赖实时同步 | 
| 股票行情推送 | ✅ 高频+低延迟 | 
| 监控告警 | ⚠️ 若频率不高可用长轮询 | 
📌 关键判断标准:
✅ 低延迟 + 高频 + 高并发 + 服务端主动推 = 推荐 WebSocket
❌ 网络环境复杂(如公共互联网上的代理)可能阻断 WebSocket(不支持 Upgrade 头)
⚙️ 四、Spring 如何支持 WebSocket?
Spring 提供了一整套 API 来开发 WebSocket 应用。
4.1 WebSocketHandler:处理 WebSocket 消息
 
最基础的接口,你可以继承它来处理连接、接收消息、关闭等事件。
public class MyHandler extends TextWebSocketHandler {@Overridepublic void handleTextMessage(WebSocketSession session, TextMessage message) {String payload = message.getPayload();session.sendMessage(new TextMessage("Echo: " + payload));}
}
注册到某个路径 /myHandler:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(new MyHandler(), "/myHandler");}
}
4.2 握手拦截器(HandshakeInterceptor)
可以在建立连接前做一些事情,比如:
- 获取用户身份(从 HTTP Session)
- 拒绝非法来源的连接
.addInterceptors(new HttpSessionHandshakeInterceptor())
还可以自定义 HandshakeHandler 控制整个握手过程。
4.3 部署问题(Deployment)
⚠️ 注意:Java 的 WebSocket 标准(JSR-356)有一个大坑:
它不能和 Spring MVC 的
DispatcherServlet共享同一个入口!
这意味着:如果你只用原生 JSR-356,就不能让所有请求(包括普通 HTTP 和 WebSocket)都走同一个 DispatcherServlet。
✅ Spring 的解决方案:
使用
RequestUpgradeStrategy,针对不同容器(Tomcat/Jetty/WebLogic 等)提供适配层,使得 WebSocket 升级请求也能被 DispatcherServlet 正常处理。
所以你可以放心地在一个 Spring MVC 应用中同时处理普通请求和 WebSocket。
4.4 服务器配置(Server Configuration)
可以设置缓冲区大小、超时时间等:
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();container.setMaxTextMessageBufferSize(8192);container.setIdleTimeout(600000); // 10分钟空闲断开return container;
}
4.5 跨域控制(Allowed Origins)
默认只允许同源访问。可配置白名单:
registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("https://yourdomain.com");
三种模式:
- 同源(默认)
- 白名单(指定域名)
- 允许所有("*")
⚠️ 注意:IE6~IE9 支持有限,尤其是 iframe 方式需要放开
X-Frame-Options。
🔄 五、SockJS:WebSocket 的降级方案(Fallback)
为什么需要 SockJS?
因为很多老旧网络环境(公司代理、防火墙)会:
- 屏蔽 Upgrade头
- 断开长时间空闲连接
导致 WebSocket 失败。
SockJS 的作用
✅ 尝试使用 WebSocket
❌ 失败则自动切换为 HTTP 模拟长连接(Streaming 或 Polling)
对外暴露相同的编程接口(W3C WebSocket API),开发者无需修改代码。
SockJS 工作流程
- 客户端请求 /info获取服务器信息
- 根据浏览器能力选择传输方式: - ✅ WebSocket(最佳)
- ✅ XHR Streaming(流式)
- ✅ Long Polling(长轮询)
 
- 所有消息走统一格式: - 开始:o
- 消息:a["hello"](JSON 数组)
- 心跳:h
- 关闭:c
 
- 开始:
URL 示例:
https://host/myapp/endpoint/{server-id}/{session-id}/websocket
https://host/myapp/endpoint/1/abc/xhr-streaming
如何启用 SockJS?
Java 配置:
registry.addHandler(myHandler(), "/myHandler").withSockJS();
XML 配置:
<websocket:mapping path="/myHandler" handler="myHandler"/>
<websocket:sockjs/>
前端引入 sockjs-client:
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script>var sock = new SockJS('/myHandler');sock.onmessage = function(e) { console.log(e.data); };
</script>
❤️ SockJS 心跳机制
防止代理认为连接“闲置”而断开。
- 默认每 25 秒发一个心跳包(h)
- 可配置 heartbeatTime
- 使用任务调度器(TaskScheduler),建议根据 CPU 核心数调整线程池
🚪 客户端断开检测
HTTP 流或长轮询下,Servlet API 不直接通知“客户端已断开”。
Spring 的做法:
定期发送心跳 → 如果写入失败 → 认为客户端已断开
日志中会有提示,避免堆栈刷屏。
🌍 CORS 支持
SockJS 在跨域时自动添加 CORS 头:
Access-Control-Allow-Origin: http://your-origin.com
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 31536000
也可以手动禁用:
.withSockJS().setSuppressCors(true)
🖥️ SockJsClient(Java 客户端)
Spring 还提供了 Java 版本的 SockJS 客户端,用于:
- 服务间通信(非浏览器)
- 压力测试(模拟大量用户)
示例:
List<Transport> transports = Arrays.asList(new WebSocketTransport(new StandardWebSocketClient()),new RestTemplateXhrTransport()
);SockJsClient client = new SockJsClient(transports);
client.doHandshake(new MyHandler(), "http://example.com/sockjs");
📦 六、STOMP:基于 WebSocket 的高级消息协议
为什么需要 STOMP?
WebSocket 只是传输层,没有定义:
- 消息类型(订阅?发布?)
- 消息目的地(发给谁?)
- 消息体结构
👉 所以我们需要一个应用层协议 —— STOMP(Simple Text Oriented Messaging Protocol)
STOMP 的核心概念
| 概念 | 说明 | 
|---|---|
| SEND | 客户端发送消息 | 
| SUBSCRIBE | 客户端订阅某个主题 | 
| MESSAGE | 服务器向客户端推送消息 | 
| /app/hello | 客户端发来的目标地址(经由注解映射) | 
| /topic/greetings | 广播主题 | 
| /user/me/private | 用户私信(点对点) | 
示例:Spring + STOMP 架构
1. 后端配置 STOMP 端点
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/ws")     // 暴露 STOMP 端点.withSockJS();         // 支持降级}@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {registry.setApplicationDestinationPrefixes("/app"); // 前缀:客户端发给服务的消息registry.enableSimpleBroker("/topic", "/queue");    // 内存消息代理,支持广播和队列}
}
2. 控制器接收消息
@Controller
public class GreetingController {@MessageMapping("/hello") // 对应 SEND 到 /app/hello@SendTo("/topic/greetings") // 返回值广播到该主题public GreetingMessage greeting(HelloMessage message) {return new GreetingMessage("Hello, " + message.getName());}
}
3. 前端使用 stomp.js
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs@6/umd/stomp.min.js"></script><script>const socket = new SockJS('/ws');const stompClient = Stomp.over(socket);stompClient.connect({}, function(frame) {stompClient.subscribe('/topic/greetings', function(message) {console.log(JSON.parse(message.body));});stompClient.send("/app/hello", {}, JSON.stringify({'name': 'Alice'}));});
</script>
✅ 总结:一张图看懂整体架构
[Browser] ↓ (HTTP Upgrade)
[Spring DispatcherServlet]↓
[WebSocket Handshake] → 成功?→ [WebSocket Transport]↓ 否[SockJS Fallback]↓[XHR Streaming / Long Polling]↑↓ 使用 STOMP 协议通信[STOMP Broker Relay 或 SimpleBroker]↑[@MessageMapping 方法处理]
🧠 关键要点总结
| 技术 | 作用 | 
|---|---|
| WebSocket | 提供全双工通信通道 | 
| SockJS | WebSocket 的兼容性兜底方案(降级) | 
| STOMP | 在 WebSocket 上构建的消息协议(类似 AMQP 的轻量版) | 
| SimpleBroker | 内置内存消息代理,适合小规模应用 | 
| External Broker(如 RabbitMQ) | 生产环境推荐,支持集群、持久化 | 
🛠️ 实际开发建议
- 开发阶段:用 @EnableWebSocketMessageBroker + SimpleBroker
- 生产环境:集成 RabbitMQ 或 ActiveMQ 作为 STOMP Broker
- 跨域:合理设置 setAllowedOrigins()和X-Frame-Options
- 安全:结合 Spring Security 控制连接权限
- 性能测试:用 SockJsClient模拟千级并发连接
如果你想进一步了解某一部分(比如“如何整合 RabbitMQ 做 STOMP 中继”或“Spring Security 如何保护 WebSocket”),欢迎继续提问!
