Netty 编解码
{Back to Index}  

Table of Contents

1 解码

1.1 大致流程

decode.png

Figure 1: 解码流程

用于解码的处理类通常是 ByteToMessageDecode 的子类,通过实现 decode/3 抽象方法来自定义解码行为。

大多数解码器会用到下面代码所示的处理模式来解码,因此具体的解码逻辑是封装在 decode/2 方法中的。

@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    Object decoded = decode(ctx, in);
    if (decoded != null) {
        out.add(decoded);
    }
}

1.2 LengthFieldBasedFrameDecoder

public LengthFieldBasedFrameDecoder(
    ByteOrder byteOrder,     // 通常使用大端字节序
    int maxFrameLength,
    int lengthFieldOffset,   // 长度域字段第一个字节在整个数据包中的偏移量
    int lengthFieldLength,   // 长度域字段字节长度(最多 4 个字节,因为 extractFrame/4 方法参数类型是 int )
    int lengthAdjustment,    // (lengthField 的值 + lengthAdjustment) 为最终确定需要读取的字节数
    int initialBytesToStrip, // 已读字节,再忽略掉前 initialBytesToStrip 个字节,最终得到需要的数据
    boolean failFast
) {...}

1.2.1 ContentLength+Content

/**
 * BEFORE DECODE                    AFTER DECODE
 * +--------+----------------+      +----------------+
 * | Length | Actual Content |----->| Actual Content |
 * | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
 * +--------+----------------+      +----------------+
 */
public static void decodeV1() {
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    serverBootstrap.group(Servers.BOSS, Servers.WORKER)
        .channel(NioServerSocketChannel.class)
        .handler(new LoggingHandler(LogLevel.INFO))
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) {
                    ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,
                                                                           0, // lengthFieldOffset
                                                                           2, // lengthFieldLength
                                                                           0, // lengthAdjustment
                                                                           2, // initialBytesToStrip
                                                                           false));
                    ch.pipeline().addLast(new StringDecoder(StandardCharsets.UTF_8));
                    ch.pipeline().addLast(StringHandler.INSTANCE);
                }
            }).bind(port);
}
// test by: corgi_misc send-length-field-based-frame --version v1

1.2.2 Header+ContentLength+Content

/**
 * BEFORE DECODE                                 AFTER DECODE
 * +----------+----------+----------------+      +----------------+
 * | Header 1 |  Length  | Actual Content |----->| Actual Content |
 * |  0xCAFE  | 0x00000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
 * +----------+----------+----------------+      +----------------+
 */
public static void decodeV2() {
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    serverBootstrap.group(Servers.BOSS, Servers.WORKER)
        .channel(NioServerSocketChannel.class)
        .handler(new LoggingHandler(LogLevel.INFO))
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) {
                    ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,
                                                                           2,  // lengthFieldOffset
                                                                           2,  // lengthFieldLength
                                                                           0,  // lengthAdjustment
                                                                           4,  // initialBytesToStrip
                                                                           false));
                    ch.pipeline().addLast(new StringDecoder(StandardCharsets.UTF_8));
                    ch.pipeline().addLast(StringHandler.INSTANCE);
                }
            }).bind(port);
}
// test by: corgi_misc send-length-field-based-frame --version v2

1.2.3 ContentLength+Header+Content

/**
 * BEFORE DECODE                                 AFTER DECODE
 * +----------+----------+----------------+      +----------------+
 * |  Length  | Header 1 | Actual Content |----->| Actual Content |
 * | 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | "HELLO, WORLD" |
 * +----------+----------+----------------+      +----------------+
 */
public static void decodeV3() {
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    serverBootstrap.group(Servers.BOSS, Servers.WORKER)
        .channel(NioServerSocketChannel.class)
        .handler(new LoggingHandler(LogLevel.INFO))
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) {
                    ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,
                                                                           0,  // lengthFieldOffset
                                                                           3,  // lengthFieldLength
                                                                           2,  // lengthAdjustment
                                                                           5,  // initialBytesToStrip
                                                                           false));
                    ch.pipeline().addLast(new StringDecoder(StandardCharsets.UTF_8));
                    ch.pipeline().addLast(StringHandler.INSTANCE);
                }
            }).bind(port);
}
// test by: corgi_misc send-length-field-based-frame --version v3

1.2.4 Header1+ContentLength+Header2+Content

/**
 * BEFORE DECODE                                  AFTER DECODE
 * +------+--------+------+----------------+      +----------------+
 * | HDR1 | Length | HDR2 | Actual Content |----->| Actual Content |
 * | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | "HELLO, WORLD" |
 * +------+--------+------+----------------+      +----------------+
 */
public static void decodeV4() {
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    serverBootstrap.group(Servers.BOSS, Servers.WORKER)
        .channel(NioServerSocketChannel.class)
        .handler(new LoggingHandler(LogLevel.INFO))
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) {
                    ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,
                                                                           1,  // lengthFieldOffset
                                                                           2,  // lengthFieldLength
                                                                           1,  // lengthAdjustment
                                                                           4,  // initialBytesToStrip
                                                                           false));
                    ch.pipeline().addLast(new StringDecoder(StandardCharsets.UTF_8));
                    ch.pipeline().addLast(StringHandler.INSTANCE);
                }
            }).bind(port);
}
// test by: corgi_misc send-length-field-based-frame --version v4

1.2.5 Header1+ PacketLength +Header2+Content

/**
 * BEFORE DECODE                                  AFTER DECODE
 * +------+--------+------+----------------+      +----------------+
 * | HDR1 | Length | HDR2 | Actual Content |----->| Actual Content |
 * | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      | "HELLO, WORLD" |
 * +------+--------+------+----------------+      +----------------+
 * length: for whole packet
 */
public static void decodeV5() {
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    serverBootstrap.group(Servers.BOSS, Servers.WORKER)
        .channel(NioServerSocketChannel.class)
        .handler(new LoggingHandler(LogLevel.INFO))
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) {
                    // ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,
                                                                           1,  // lengthFieldOffset
                                                                           2,  // lengthFieldLength
                                                                           -3, // lengthAdjustment
                                                                           4,  // initialBytesToStrip
                                                                           false));
                    ch.pipeline().addLast(new StringDecoder(StandardCharsets.UTF_8));
                    ch.pipeline().addLast(StringHandler.INSTANCE);
                }
            }).bind(port);
}
// test by: corgi_misc send-length-field-based-frame --version v5

2 编码

2.1 大致流程

encoder.png

Figure 2: 调用 writeAndFlush(msg) 的大致流程

如果调用的是 channel.writeAndFlush() 则写操作从 TailContext 开始传播,
如果调用的是 ctx.writeAndFlush()= 则写操作从下一个 handlerContext 开始传播。
但不论传播的起点在哪里,最终落脚点都是 HeadContext 。

对写操作 (write) 而言,业务对象转为字节的操作是在自定义解码器中完成的,随后传递至 HeadContext 并缓存到发送队列 (outboundBuffer) 中。
对刷新操作 (flush) 而言,具体行为也是由 HeadContext 完成的,最终发送至底层 JDK socket buffer 。

Author: Hao Ruan (ruanhao1116@gmail.com)

Created: 2022-06-08 Wed 20:49

Updated: 2022-06-09 Thu 18:17

Emacs 27.2 (Org mode 9.4.4)