Netty笔记:ByteToMessageDecoder源码简单分析

898 阅读3分钟

简单介绍

ByteToMessageDecoder是将入栈处理器中的字节流解码成其他类型消息,它是一个解码器,而且是所有解码器的顶级父类。ByteToMessageDecoder是一个抽象类,所以可以通过继承来实现自己定义的解码器。
简单来说,ByteToMessageDecoder的大致工作流程是将入栈的消息存放到一个缓冲区累加器中,通过不断的调用子类的decode方法,读取累加器中的数据,将转换后的消息放到一个对象池中,直到缓冲区累加器的数据读完(或者其他原因),然后遍历对象池,依次把转换后的消息传递给下一个处理器。下面来具体分析。

Cumulator 累加器

ByteToMessageDecoder提供了2种累加器把ByteBuf累加起来,MERGE_CUMULATORCOMPOSITE_CUMULATOR
MERGE_CUMULATOR:通过内存复制的方式累加新的ByteBuf,是ByteToMessageDecoder默认的Cumulator。

final ByteBuf buffer;
if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
    || cumulation.refCnt() > 1 || cumulation.isReadOnly()) {
    // 当缓冲区中没有更多空间,或引用计数器大于1(调用retain()方法)或缓冲区只读,拓展累积缓冲区
    buffer = expandCumulation(alloc, cumulation, in.readableBytes());
} else {
    buffer = cumulation;
}
// 复制写入新的ByteBuf
buffer.writeBytes(in);
return buffer;
static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf cumulation, int readable) {
    ByteBuf oldCumulation = cumulation;
    // 生成一个更大的缓冲区来替换之前的
    cumulation = alloc.buffer(oldCumulation.readableBytes() + readable);
    cumulation.writeBytes(oldCumulation);
    oldCumulation.release();
    return cumulation;
}

COMPOSITE_CUMULATOR:复合缓冲区模式,实际上是用数组保存多个ByteBuf,尽可能不进行内存复制,但是存在复杂的索引管理,所以性能比MERGE_CUMULATOR稍低。

CompositeByteBuf composite;
if (cumulation instanceof CompositeByteBuf) {
    composite = (CompositeByteBuf) cumulation;
} else {
    // 创建一个CompositeByteBuf
    composite = alloc.compositeBuffer(Integer.MAX_VALUE);
    composite.addComponent(true, cumulation);
}
composite.addComponent(true, in);
in = null;
buffer = composite;

重要的成员属性(变量)

解码器的状态

private byte decodeState = STATE_INIT;

// 初始状态,等待缓冲区读数据
private static final byte STATE_INIT = 0;
// 调用子类解码中
private static final byte STATE_CALLING_CHILD_DECODE = 1;
// 解码器待移除
private static final byte STATE_HANDLER_REMOVED_PENDING = 2;
// 每次解码一条消息
private boolean singleDecode;

// 解码次数
private int numReads;
// 当解码次数达到16次时,丢弃累积器里面已读的数据,避免OOM
private int discardAfterReads = 16;

主要方法

channelRead

解码器也是一个入栈handler,入栈消息首先调用的就是这个方法,先分析这个方法(主要代码):

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    // 解码后的消息对象池,实现List接口,是线程安全的
    CodecOutputList out = CodecOutputList.newInstance();
    try {
        ByteBuf data = (ByteBuf) msg;
        first = cumulation == null;
        if (first) {
            // 第一次解码
            cumulation = data;
        } else {
            // 将新的ByteBuf追加到cumulator中,然后释放这个ByteBuf
            cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
        }
        // 循环调用子类的模板方法decode
        callDecode(ctx, cumulation, out);
    } finally {
        if (cumulation != null && !cumulation.isReadable()) {
            // 累加器没有可读数据,计数器归零,清空累加器,显式置空等待被回收
            numReads = 0;
            cumulation.release();
            cumulation = null;
        } else if (++ numReads >= discardAfterReads) {
            // 解码次数达到16次,丢弃累加器里面已经被子类decode读取过的数据
            numReads = 0;
            discardSomeReadBytes();
        }

        int size = out.size();
        // 判断对象池out是不是空的,如果有数据,则调用ctx.fireChannelRead()将消息传递给下一个handler
        decodeWasNull = !out.insertSinceRecycled();
        fireChannelRead(ctx, out, size);
        // 清空对象池
        out.recycle();
    }
}

callDecode

当累加器有可读数据,通过callDecode调用子类decode进行解码

while (in.isReadable()) {
    int outSize = out.size();
    // 解码后对象池有数据,传递消息到下一个handler,清空对象池
    if (outSize > 0) {
        fireChannelRead(ctx, out, outSize);
        out.clear();
        
        outSize = 0;
    }

    // 记录子类decode之前的累加器中的可读数据字节数
    int oldInputLength = in.readableBytes();
    // 调用子类decode解码,解码后的数据放到对象池中
    decodeRemovalReentryProtection(ctx, in, out);

    // 这里outSize=0,没有解码出数据
    if (outSize == out.size()) {
        if (oldInputLength == in.readableBytes()) {
            // 没有解码出数据,也没有移动读写索引
            break;
        } else {
            // 有读取数据,说明子类在解码,继续解码
            continue;
        }
    }

    // 到这里outSize>0,但是没有读取数据,说明子类decode操作不正确,抛出异常
    if (oldInputLength == in.readableBytes()) {
        throw new DecoderException(
                StringUtil.simpleClassName(getClass()) +
                        ".decode() did not read anything but decoded a message.");
    }

    if (isSingleDecode()) {
        // 单次解码器就跳出循环
        break;
    }
}

channelInputClosed

// 当channel关闭时调用
void channelInputClosed(ChannelHandlerContext ctx, List<Object> out) throws Exception {
    if (cumulation != null) {
        // 如果累加器还有可读数据,继续解码
        callDecode(ctx, cumulation, out);
        decodeLast(ctx, cumulation, out);
    } else {
        // 最后解码一次,这个方法可以被重写处理特殊的逻辑,比如写一个LastHttpContent消息
        decodeLast(ctx, Unpooled.EMPTY_BUFFER, out);
    }
}