websocket.send()本身线程安全,但多模块直发会导致消息顺序错乱、格式混杂;须统一消息出口、自动注入id/ts/type字段、节流非关键消息,并用帧头解决二进制粘包问题。

JavaScript 中 WebSocket 本身不处理客户端并发发送消息的冲突,因为浏览器端的 WebSocket.send() 是同步、非阻塞且线程安全的——它只是把数据交给底层网络栈排队,不会抛出竞态异常。但真正的问题不在“发不出”,而在于多个逻辑模块同时调用 send() 时,消息顺序错乱、结构混杂或覆盖状态。解决重点是应用层协调,而非加锁或等待。
避免多处直接调用 send() 导致混乱
前端常见错误:聊天模块、通知模块、心跳模块各自持有 socket 实例并独立调用 send(),结果消息体格式不统一、序列号缺失、甚至 JSON 字符串被截断(尤其在快速连续发送时)。
- 统一出口:所有业务消息必须经过一个中央消息发送器(如
MessageBus类),禁止任何模块直连socket.send() - 强制封装:每条消息自动注入公共字段,如
id(唯一请求 ID)、ts(毫秒时间戳)、type(业务类型),便于服务端排序与幂等校验 - 限制频率:对非关键消息(如光标位置、打字状态)做节流(throttle)或防抖(debounce),避免短时密集发送
处理二进制消息的粘包与半包问题
当使用 ArrayBuffer 或 Uint8Array 发送自定义二进制协议时,onmessage 回调收到的数据可能不是完整帧——这是 TCP 层特性,不是 WebSocket Bug。
- 前端无需拼接 buffer:浏览器已保证单次
onmessage触发对应一个完整 WebSocket 帧(文本或二进制),但需注意:服务端若分片发送,客户端仍会收到多个独立事件 - 二进制协议必须自带帧头:例如前 4 字节为 payload 长度,接收端据此判断是否收齐;否则无法区分两条连续消息
- 不要依赖
event.data instanceof ArrayBuffer做逻辑分支:应统一用new Uint8Array(event.data)解析,兼容 Blob 和 ArrayBuffer
保障关键操作的原子性与顺序
某些场景要求严格时序,比如协作文档中“先插入再删除”不能颠倒。客户端无法控制网络传输顺序,只能靠协议设计兜底。
- 带序号操作:每个编辑操作携带递增
seq,服务端按序合并;客户端本地也维护已发出操作队列,重试时跳过已确认项 - 阻塞式发送(仅限强一致性场景):封装
sendAndWaitAck()方法,内部用 Promise + 消息 ID 匹配响应,期间暂停同通道其他发送 - 心跳与业务消息分离:心跳用
socket.ping()(如果支持)或空字符串,不走主消息通道,避免干扰业务队列
连接状态变化时的安全兜底
用户切换页面、网络闪断、或 socket 关闭中调用 send() 会静默失败(无异常,但消息丢失)。这比并发更隐蔽,也更常发生。
- 发送前必检状态:
if (socket.readyState === WebSocket.OPEN) { socket.send(...) },拒绝CLOSING和CLOSED状态 - 建立待发队列:发送失败时缓存消息,监听
reconnect事件后批量重发(需服务端支持幂等) - 超时机制:对带响应的消息设置 5s 超时,未收到 ack 则标记失败并触发降级逻辑(如弹窗提示“发送延迟”)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











