WebSocketServerProtocolHandler仅处理标准握手、帧封装与心跳,不负责私有协议编解码;需在其后添加MessageToMessageDecoder/Encoder处理业务消息,不可直接使用ByteToMessageDecoder。
websocketserverprotocolhandler 不是万能的,它只处理标准 websocket 协议握手、帧封装和心跳,不负责业务消息的编解码。如果你需要在 websocket 之上跑私有协议(比如带指令头、长度域、加密字段),必须在 websocketserverprotocolhandler 之后再加自定义编解码器。
为什么不能直接在 WebSocket 上用 ByteToMessageDecoder?
因为 WebSocketServerProtocolHandler 已经把原始 ByteBuf 解包成 TextWebSocketFrame 或 BinaryWebSocketFrame,后续 handler 收到的是「已解帧」的对象,不是裸字节流。
常见错误现象:
- 在 WebSocketServerProtocolHandler 前插入 ByteToMessageDecoder → 解码器收不到数据(被前面的 HTTP/WS handler 吃掉了)
- 在 WebSocketServerProtocolHandler 后插入 ByteToMessageDecoder → decode() 方法入参类型是 TextWebSocketFrame,但你试图调用 in.readableBytes() → 报 ClassCastException 或空指针
- 正确做法:继承
MessageToMessageDecoder,泛型指定为TextWebSocketFrame或BinaryWebSocketFrame - 解码目标对象应是你自己的业务 POJO(如
LoginRequest、ChatMessage) - 编码器同理,用
MessageToMessageEncoder,输入是 POJO,输出是TextWebSocketFrame或BinaryWebSocketFrame
如何实现带长度+指令头的 BinaryWebSocketFrame 编解码?
适用于二进制私有协议(如 Protobuf、FlatBuffer 序列化体),需解决粘包/半包问题 —— 但注意:WebSocket 帧本身已有边界,所以「粘包」发生在应用层协议内(即一个 BinaryWebSocketFrame 里可能含多条业务消息,或一条消息跨多个帧)。实际中更常见的是「单帧单消息」,所以重点在帧内解析。
典型结构:[int length][short cmd][byte[] body]
- 解码器(
MessageToMessageDecoder<BinaryWebSocketFrame>):- 先
frame.content().readableBytes()检查是否 ≥ 6 字节(4+2) - 用
frame.content().slice()截出 length 和 cmd,再截出 body - 用
ByteBuf.readBytes(int)拷贝 body 到新数组,反序列化为 POJO - 若长度不足,
ctx.channel().config().setAutoRead(false)暂停读取,等下个帧来拼接(需自行缓存 buffer)
- 先
- 编码器(
MessageToMessageEncoder<MyMessage>):-
msg.toByteArray()得到 body -
ctx.alloc().buffer(6 + body.length)分配空间 - 依次
writeInt(body.length)、writeShort(cmd)、writeBytes(body) - 封装为
new BinaryWebSocketFrame(byteBuf)加入out
-
TextWebSocketFrame 场景下如何安全解析 JSON?
文本帧本质是 UTF-8 字符串,看似简单,但容易踩坑:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 客户端发的 JSON 可能换行、缩进、含 BOM 头 →
frame.text()直接调用会失败 - 大 JSON 被分片发送(
ContinuationWebSocketFrame)→ 单个TextWebSocketFrame不完整 - 没做最大长度限制 → OOM 风险
实操建议:
- 在
WebSocketServerProtocolHandler后加HttpObjectAggregator(设maxContentLength=1024*1024),强制合并分片帧 - 解码器继承
MessageToMessageDecoder<TextWebSocketFrame>,用frame.text().trim()去空格后 parse - 用 Jackson 的
ObjectMapper.readValue(String, Class),捕获JsonProcessingException并关闭 channel - 避免用
frame.content().toString(CharsetUtil.UTF_8)—— 它不处理 BOM,且忽略TextWebSocketFrame.isFinalFragment()
共享与线程安全:@ChannelHandler.Sharable 能乱加吗?
很多教程无脑加 @ChannelHandler.Sharable,但这是危险操作:
-
MessageToMessageCodec类如果持有非线程安全状态(如StringBuilder、缓存ByteBuf、未同步的 map)→ 多 channel 并发访问时数据错乱 -
WebSocketServerProtocolHandler本身是 sharable 的,但你的自定义编解码器通常要维护 per-channel 状态(如解码缓冲区) - Netty 4.1+ 默认每个
ChannelPipeline实例化 handler 新对象;只有显式标注@Sharable才复用
结论:除非你确认 handler 是纯函数式(无任何可变成员变量),否则不要加 @ChannelHandler.Sharable。尤其涉及 buffer 缓存、计数器、上下文 map 的场景,必须每个 channel 独享实例。

















