Skip to content

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字节转字符串

自定义协议

常见二进制协议可以按下面结构设计:

字段长度说明
magic2 字节魔数,快速识别协议
version1 字节协议版本
type1 字节请求、响应、心跳
length4 字节body 长度
bodyN 字节序列化后的业务数据

处理流程

mermaid
flowchart TD
    A[读取字节] --> B{可读长度是否够头部}
    B -- 否 --> C[等待更多数据]
    B -- 是 --> D[校验魔数和版本]
    D --> E{body是否完整}
    E -- 否 --> C
    E -- 是 --> F[反序列化业务对象]
    F --> G[交给业务Handler]

开发建议

  1. 解码器中不要阻塞调用数据库或远程接口。
  2. 协议必须有长度字段或明确分隔符,否则无法稳定拆包。
  3. 编码和解码要成对测试,尤其关注空 body、大包、非法 magic。
  4. 协议升级时保留 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));
    }
}