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