netty的websocket13framedecoder自动处理websocket帧级粘包/半包,但业务消息需自定义长度前缀协议(如4字节len+2字节cmd)并用lengthfieldbasedframedecoder解析。

WebSocket 本身不解决业务层粘包/分包,但 Netty 的 WebSocket13FrameDecoder 已自动处理 WebSocket 帧级的边界问题;真正需要你动手的,是帧内业务消息的粘包——也就是你在 BinaryWebSocketFrame 里塞了多条业务数据,或没按协议头解析 body。
Netty 中 WebSocket 帧解码器是否自动处理 TCP 层粘包
是的,WebSocket13FrameDecoder(由 WebSocketServerProtocolHandler 自动注册)会严格按 RFC 6455 解析 FIN、opcode、mask 和 payload length 字段,并完成:
- 多个 TCP segment 合并成一个完整 WebSocket 帧(防半包)
- 一个大帧被 TCP 拆成多个 segment 时缓存重组(防拆包)
- 连续多个小帧(如两个 BinaryWebSocketFrame)不会混在一起,每个都独立触发 channelRead
这意味着:你收到的每个 BinaryWebSocketFrame 都是协议层面“完整且独立”的帧,frame.content().readableBytes() 就是该帧的 payload 全长。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
但注意:
- 如果你在客户端把两条 JSON 拼成一个字符串再发("{...}{...}"),服务端收到后仍是一个帧,但业务层无法自动切开 → 这属于你自己的协议设计问题
- 如果用 Netty 手动写入 new BinaryWebSocketFrame(Unpooled.wrappedBuffer(buf1, buf2)),也等同于拼包,解码器照单全收
为什么还要在 WebSocket 帧里加长度前缀
因为 WebSocket 帧只保证“帧完整”,不保证“业务消息完整”。常见场景:
- IM 推送:一次推送 3 条消息,打包进一个二进制帧提升吞吐
- 实时信令:command=LOGIN + command=JOIN_ROOM 合并在一帧中减少 RTT
- 序列化数据:Protobuf 或自定义二进制结构,无分隔符,必须靠长度字段定位
此时必须在帧 payload 内部定义子协议,典型结构为:[len:4][cmd:2][body]
- len 是 body 字节数(非 Java String 长度),网络字节序(Big-Endian)
- cmd 是命令类型,可选,但影响 lengthAdjustment 配置
- body 是实际序列化后的内容,长度必须等于 len
不加长度前缀的后果:
- 用 readBytes(frame.content().readableBytes()) 直接读全帧 → 只能当单条消息处理,无法支持批量
- 用 readableBytes() 判断再循环读 → 没有长度字段,根本不知道下一条从哪开始
LengthFieldBasedFrameDecoder 配置关键参数怎么填
假设协议头是 [len:4][cmd:2][body],且 len 字段值仅表示 body 长度(不含 cmd),那么:
- maxFrameLength = 4 * 1024 * 1024:设硬上限防 OOM
- lengthFieldOffset = 0:长度字段从 payload 起始位置开始
- lengthFieldLength = 4:int32 占 4 字节
- lengthAdjustment = 2:因为 len 不含 cmd 字段,但整个 header 占 6 字节,decoder 需跳过 len+cmd 才到 body 起始,所以要补上 cmd 的长度
- initialBytesToStrip = 6:解码后丢弃整个 header(4+len + 2-cmd),只留 body
常见配错点:
- 把 lengthAdjustment 写成 0 → decoder 以为 len 包含 cmd,导致后续 body 偏移错位
- initialBytesToStrip 设成 4 → cmd 字段残留在解码结果开头,业务解析失败
- 用 ByteBuf.readShort() 读 cmd 时没调用 readSkip(4) → 下次读 len 会错位
数据完整性校验该加在哪一层
CRC 或 MD5 校验不应加在 TCP 层(不可控),也不建议加在 WebSocket 帧头(标准不支持),最佳位置是业务协议头尾之间,例如:[len:4][cmd:2][crc:2][body][crc_tail:2]。但更轻量的做法是:
- 在 body 序列化前计算 CRC,追加到 body 末尾
- 解码后先截出最后 2 字节作为期望 CRC,再对前面部分重新计算比对
- 校验失败直接丢弃该条业务消息,不抛异常(避免中断整个帧流)
注意:
- 不要用 String.getBytes("UTF-8") 后算 CRC → 必须用序列化后的原始 byte[]
- 如果用了压缩(如 snappy),CRC 必须在压缩后、加长度头前计算
- WebSocket 帧本身有掩码(客户端→服务端),但那是防中间人嗅探,不是完整性保护,服务端解帧后掩码已去除,不影响业务层校验
真正容易被忽略的是:WebSocket 帧解码和业务协议解码是两层事,前者由 Netty 自动完成,后者必须你亲自定义 LengthFieldBasedFrameDecoder 并确保参数与协议头完全对齐;任何一处字节偏移错位,都会导致后续所有消息解析雪崩。










