websocket协议在帧层面已解决tcp粘包分包问题,浏览器api保证每个message事件的data均为完整帧(字符串或arraybuffer/blob),但业务层若在一帧内打包多条消息,则需自行按长度前缀、换行符等约定解析,否则会出现业务粘包。

WebSocket 协议本身在帧层面已解决 TCP 层的粘包和分包问题,但 JavaScript(浏览器端)的 WebSocket API 并不暴露帧结构,也不提供自动拆帧或协议解析能力。你收到的 message 事件中,data 是应用层视角的完整 payload——它可能是:
- 一个完整的文本帧(
string) - 一个完整的二进制帧(
ArrayBuffer或Blob) - 但绝不会是半个帧,也绝不会把两个帧拼成一个
data
也就是说:浏览器端不存在 WebSocket 帧级粘包/分包,这是由底层实现(遵循 RFC 6455)严格保证的。
真正需要你处理的,是「业务消息粘包」——即你在一帧里塞了多条业务数据(比如连续发了两个 JSON 对象、或打包了多个 Protobuf 消息),而浏览器照单全收,全给你塞进同一个 message 里。
为什么你会遇到“粘包”现象?
常见错误写法:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
// ❌ 错误:手动拼接多个 JSON 字符串再 send
ws.send('{"cmd":"login"}{"cmd":"join","room":"a1"}'); // 一个字符串,无分隔
或二进制场景:
// ❌ 错误:用 Uint8Array 手动拼接多个消息体,没加长度头或分隔符
const buf1 = encodeMsg({ cmd: 'ping' });
const buf2 = encodeMsg({ cmd: 'ready' });
const combined = new Uint8Array([...buf1, ...buf2]);
ws.send(combined.buffer); // 浏览器当做一个 BinaryWebSocketFrame 发出
→ 服务端收到的是一个完整帧,但帧内含两条业务消息,没有边界信息,自然无法拆开。
前端如何正确避免/处理业务层粘包
-
✅ 发送端约定协议格式(推荐)
在每条业务消息前加 4 字节大端长度(Uint32Array),例如:function pack(msg) { const encoder = new TextEncoder(); const body = encoder.encode(JSON.stringify(msg)); const len = new Uint32Array([body.length]); // 网络字节序(Big Endian) const buffer = new ArrayBuffer(4 + body.length); const view = new DataView(buffer); view.setUint32(0, body.length); // 写入长度头 new Uint8Array(buffer, 4).set(body); // 写入 body return buffer; } ws.send(pack({ cmd: 'login', uid: 1001 })); ws.send(pack({ cmd: 'chat', text: 'hello' })); -
✅ 接收端做流式缓冲与按协议解析
message事件可能触发多次,每次data是独立帧;但若后端把多条消息打在一个帧里发过来,你就要自己切:let buffer = new Uint8Array(); // 全局缓存 ws.onmessage = (e) => { const incoming = e.data instanceof ArrayBuffer ? new Uint8Array(e.data) : new TextEncoder().encode(e.data); buffer = concatUint8Array(buffer, incoming); // 循环解析:有足够字节读长度头 → 读 body → 切出来 → 继续 while (buffer.length >= 4) { const len = new DataView(buffer.buffer).getUint32(0); // 大端 if (buffer.length -
✅ 用换行符分隔(适合纯文本)
简单场景可约定每条消息以\n结尾:// 发送 ws.send(JSON.stringify({ cmd: 'login' }) + '\n'); // 接收(需按行分割) let pending = ''; ws.onmessage = (e) => { pending += typeof e.data === 'string' ? e.data : new TextDecoder().decode(e.data); const lines = pending.split('\n'); pending = lines.pop(); // 最后一行可能不完整 for (const line of lines) { if (line.trim()) onBusinessMessage(JSON.parse(line)); } };
哪些做法要避免
- 不要假设
onmessage的data一定是一条业务消息(尤其后端做了批量打包) - 不要每次
message都JSON.parse(data)—— 若 data 是二进制或含多条 JSON,会直接报错 - 不要用
Buffer.concat或Uint8Array.join()等粗暴拼接方式处理未定义协议的二进制流 - 不要在前端依赖“发送几次就触发几次 message”——WebSocket 允许服务端合并多条业务消息到一帧(合法且高效)
不复杂但容易忽略:粘包不是 WebSocket 的 bug,而是你和后端之间缺少明确的子协议约定。只要双方统一用长度前缀、换行分隔或固定长度,前端就能稳稳解包。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










