Netty 系列 解码器

Posted by lichao modified on September 24, 2019

将字节流转换成消息的解码器

Netty解码器有很多种,比如基于长度的、基于分割符的、私有协议的,但是总体的思路都是一致的。

  • 拆包思路:当数据满足了解码条件时,将其拆开。放到数组,然后发送到业务 handler 处理。
  • 半包思路:当读取的数据不够时,先存起来,直到满足解码条件后,放进数组,送到业务 handler 处理。

概念

frame 帧

解析器

一般情况下,通过添加***FrameDecoder帧解码器,在管道pipeline中尽早地处理数据。

  • DelimiterBasedFrameDecoder:以某个分割符为一帧
  • FixedLengthFrameDecoder:以固定长度字节为一帧
  • LineBasedFrameDecoder:以换行符为一帧
  • LengthFieldBasedFrameDecoder:这种针对,协议有不同的部分组成,如头部,数据部分,头部中包含数据长度信息的情况。

ByteToMessageDecoder

  • 所有ByteToMessageDecoder解码器的子类不能被Sharable注解
  • 如果ByteBuf.readBytes(int)方法的返回buffer没有被释放,可能造成内存泄漏

channelRead 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    // 从对象池中取出一个空的List
    CodecOutputList out = CodecOutputList.newInstance();
    ByteBuf data = (ByteBuf) msg;
    first = cumulation == null;
    if (first) {
        // 第一次解码
        cumulation = data;// 累计
    } else {
        // 第二次解码,就将 data 向 cumulation 追加,并释放 data
        cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
    }
    // 得到追加后的 cumulation 后,调用 decode 方法进行解码
    // 解码过程中,调用 fireChannelRead 方法,主要目的是将累积区的内容 decode 到 数组中
    callDecode(ctx, cumulation, out);

    // 如果累计区没有可读字节了
    if (cumulation != null && !cumulation.isReadable()) {
        // 将次数归零
        numReads = 0;
        // 释放累计区
        cumulation.release();
        // 等待 gc
        cumulation = null;

    } // 如果超过了 16 次,就压缩累计区,主要是将已经读过的数据丢弃,将 readIndex 归零。
    else if (++ numReads >= discardAfterReads) {
        numReads = 0;
        discardSomeReadBytes();
    }

    int size = out.size();
    // 如果没有向数组插入过任何数据
    decodeWasNull = !out.insertSinceRecycled();
    // 循环数组,向后面的 handler 发送数据,如果数组是空,那不会调用
    fireChannelRead(ctx, out, size);
    // 将数组中的内容清空,将数组的数组的下标恢复至原来
    out.recycle();

}