websocket是全双工通信协议,建立“常开隧道”实现双向实时通信;http则是单次请求-响应模式,连接即断。

WebSocket 不是“学会了就能用”,而是“用的时候才发现漏了关键链路”。真正卡住开发者的,从来不是连接建立,而是消息收发过程中那些隐性依赖:会话状态管理、异常重连边界、二进制/文本帧混用、心跳缺失导致的静默断连——这些在 onMessage 回调里根本看不出来。
WebSocket.onMessage 只接收字符串?那二进制数据怎么办
默认情况下,onMessage 回调收到的 event.data 类型取决于服务端发送的内容。如果后端发的是 String,前端拿到的就是字符串;如果发的是 byte[] 或 Blob,前端拿到的就是 Blob 或 ArrayBuffer,不会自动转成字符串。
- 不显式设置
ws.binaryType = 'arraybuffer',收到二进制时可能触发TypeError: Failed to execute 'atob' on 'Window'(尤其在 base64 解码逻辑里) - 服务端用 Spring WebSocket 发送
TextMessage,前端一定收到string;发BinaryMessage,前端必须提前设binaryType才能安全读取 - 混合场景建议统一约定:文本走
string,图片/音频/协议包走ArrayBuffer,并在首字节或前 4 字节嵌入类型标识
Spring Boot 中 @OnMessage 方法参数类型决定消息解析方式
Spring 的 @OnMessage 不是只认 String。它根据方法签名自动匹配并反序列化,但规则很具体:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 参数为
String→ 自动 UTF-8 解码,抛DecodeException若非法字节 - 参数为
byte[]→ 原始字节透传,不解析,适合自定义协议 - 参数为自定义类(如
ChatMessage)→ 需注册Decoder.Text<chatmessage></chatmessage>,否则直接报 400 - 没写
@OnError且解码失败,连接会静默关闭,日志只打印Failed to decode message,无堆栈
为什么 onclose 触发时 session 已经不可用
@OnClose 方法执行时,Session 对象已处于关闭状态,调用 session.getBasicRemote().sendText() 会抛 IllegalStateException: The session is closed。这不是 bug,是 JSR-356 规范明确要求的行为。
- 想在断开前发最后一条消息,必须在
@OnMessage或业务逻辑中预判断连(比如收到{"type":"logout"})并主动发送 - 清理资源(如从
ConcurrentHashMap移除 session)必须放在@OnClose里,但不能依赖 session 本身做 I/O - 浏览器刷新或关页时,
@OnClose可能不触发(TCP FIN 未送达),所以服务端要配超时踢出 + 心跳检测
生产环境必须关掉的三个默认行为
Spring Boot 内置的 WebSocket 配置在开发时够用,上线后不调整会埋雷:
- 禁用默认的
SimpleBroker:它用内存 Map 存订阅关系,集群下失效,必须换RedisMessageBroker或ActiveMQ - 关闭
setAllowedOrigins("*"):CORS 允许任意源会引发跨站攻击,应精确配置如["https://app.example.com"] - 限制
maxTextMessageSize和maxBinaryMessageSize:默认不限,大文件上传可能 OOM;建议设为1024 * 1024(1MB)并配合前端分片
最常被忽略的是心跳帧的处理粒度:Ping/Pong 是 TCP 层保活,但业务层需要独立的心跳消息(如每 30s 交互 {"type":"ping"}),否则 NAT 网关可能在 60s 后单向切断连接,客户端毫无感知。










