WebSocket 不支持后端主动发起 HTTP 请求,仅能通过已建立的长连接向客户端发送数据;其本质是传输通道,不具备 HTTP 的重试、超时、状态码等语义,后端调第三方接口需先用 RestTemplate/WebClient,再用 WebSocket 推送结果。

WebSocket 本身不支持“后端主动发起 HTTP 请求”——它不是用来替代 RestTemplate 或 WebClient 的。后端主动“推送消息” ≠ 主动发 HTTP 请求,而是通过已建立的长连接,调用 session.getBasicRemote().sendText() 等方法向客户端写数据。这是关键前提,混淆这点会导致设计跑偏。
为什么不能用 WebSocket 替代 HTTP 调用?
WebSocket 是传输通道,不是协议封装器:
- 它没有内置重试、超时、状态码、Header、Cookie 自动携带等 HTTP 语义
- 后端无法通过它“请求第三方接口”,只能往自己已连接的某个 Session 发送字符串/字节流
- 若你真正想做的是“后端调第三方 API 再把结果推给前端”,那得拆成两步:HTTP 调用 + WebSocket 推送,不能混为一谈
Spring Boot 中怎么向指定用户推送消息?
核心是拿到目标用户的 WebSocketSession 实例。常见做法有:
- 连接时用 URL 参数(如
/ws?userId=1001)或 Header 携带标识,在HandshakeInterceptor中校验并存入session.getAttributes() - 用
ConcurrentHashMap<string websocketsession></string>缓存在线会话,key 为 userId,注意并发安全和连接断开后的清理(靠@OnClose或心跳检测) - 避免直接用
@Scheduled定时扫表推送——这会绕过连接状态判断,可能向已断连的 session 发送失败,抛IllegalStateException: The session has been closed - 推送前务必检查
session.isOpen(),否则会触发异常且无日志提示(默认静默丢弃)
前端如何稳定接收后端推送?
浏览器原生 WebSocket 对象不自动重连,必须手动实现:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 监听
onclose事件后延迟几秒再 new WebSocket(),避免雪崩式重连 - 不要在
onerror里直接重连——该事件可能由临时网络抖动触发,但连接实际仍有效;应优先以onclose为准 - 服务端需配合发送心跳帧(如每 30s
sendPing()),前端收到onmessage含特定 ping payload 时刷新活跃标记 - URL 必须用
wss://(生产)或ws://(开发),若走 Nginx,需配置proxy_http_version 1.1和Upgrade $http_upgrade,否则握手 400
容易被忽略的坑:Session 生命周期与线程安全
WebSocketSession 不是线程安全的,且不能跨线程复用:
- 不能在异步线程(如
@Async方法、CompletableFuture回调)中直接调用session.sendText()—— 可能抛java.lang.IllegalStateException: Session is not open - 正确做法:把要推送的数据和 session ID 发到线程安全队列(如
ConcurrentLinkedQueue),由单个调度线程轮询并逐个发送 - Spring 的
SimpMessagingTemplate可简化广播,但它底层仍是基于 session 查找,同样依赖缓存有效性 - 每次连接断开后,必须从缓存中移除对应 session,否则内存泄漏 + 推送失败静默
真正的难点不在“怎么连”,而在“怎么管住每一个 session 的生老病死”。连接建立只是开始,后续的保活、清理、路由、降级,才是 WebSocket 在生产环境落地的分水岭。

















