Netty 编解码
{Back to Index}
Table of Contents
1 解码
1.1 大致流程
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 大致流程
Figure 2: 调用 writeAndFlush(msg) 的大致流程
如果调用的是 channel.writeAndFlush()
则写操作从 TailContext 开始传播,
如果调用的是 ctx.writeAndFlush()=
则写操作从下一个 handlerContext 开始传播。
但不论传播的起点在哪里,最终落脚点都是 HeadContext 。
对写操作 (write) 而言,业务对象转为字节的操作是在自定义解码器中完成的,随后传递至 HeadContext 并缓存到发送队列 (outboundBuffer) 中。
对刷新操作 (flush) 而言,具体行为也是由 HeadContext 完成的,最终发送至底层 JDK socket buffer 。