Netty 编解码
编解码负责在“字节流”和“业务对象”之间转换。网络上传输的一定是字节,业务代码更适合处理对象,所以编解码层是 Netty 项目的核心边界。
数据流向
mermaid
flowchart TD
A["Socket 字节"] --> B["ByteToMessageDecoder<br/>解码"]
B --> C["业务请求对象"]
C --> D["业务 Handler"]
D --> E["响应对象"]
E --> F["MessageToByteEncoder<br/>编码"]
F --> G["Socket 字节"]常见组件
| 组件 | 作用 |
|---|---|
ByteToMessageDecoder | 把字节拆成消息对象 |
MessageToByteEncoder | 把对象编码成字节 |
MessageToMessageDecoder | 对消息对象再次转换 |
LengthFieldBasedFrameDecoder | 按长度字段解决拆包粘包 |
StringDecoder | 字节转字符串 |
自定义协议
常见二进制协议可以按下面结构设计:
| 字段 | 长度 | 说明 |
|---|---|---|
| magic | 2 字节 | 魔数,快速识别协议 |
| version | 1 字节 | 协议版本 |
| type | 1 字节 | 请求、响应、心跳 |
| length | 4 字节 | body 长度 |
| body | N 字节 | 序列化后的业务数据 |
处理流程
mermaid
flowchart TD
A[读取字节] --> B{可读长度是否够头部}
B -- 否 --> C[等待更多数据]
B -- 是 --> D[校验魔数和版本]
D --> E{body是否完整}
E -- 否 --> C
E -- 是 --> F[反序列化业务对象]
F --> G[交给业务Handler]开发建议
- 解码器中不要阻塞调用数据库或远程接口。
- 协议必须有长度字段或明确分隔符,否则无法稳定拆包。
- 编码和解码要成对测试,尤其关注空 body、大包、非法 magic。
- 协议升级时保留 version 字段,避免新旧客户端互相不兼容。
代码 Demo:自定义编码器
java
public class MessageEncoder extends MessageToByteEncoder<String> {
private static final short MAGIC = (short) 0xCAFE;
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) {
byte[] body = msg.getBytes(StandardCharsets.UTF_8);
out.writeShort(MAGIC);
out.writeByte(1);
out.writeInt(body.length);
out.writeBytes(body);
}
}对应解码器要按同样顺序读取:
java
public class MessageDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (in.readableBytes() < 7) {
return;
}
in.markReaderIndex();
short magic = in.readShort();
byte version = in.readByte();
int length = in.readInt();
if (magic != (short) 0xCAFE) {
ctx.close();
return;
}
if (in.readableBytes() < length) {
in.resetReaderIndex();
return;
}
byte[] body = new byte[length];
in.readBytes(body);
out.add(new String(body, StandardCharsets.UTF_8));
}
}