上一节课我们将常用的解码器包括行解码器、自定义分隔符解码器、定长解码器、不定长解码器、自定义解码器的基本使用,本节课我们分析下这几个解码器的源码!
我们看一下上述解码器的继承关系:
我们发现无论是内置解码器还是我们自定义解码器,都必定会继承一个抽象类ByteToMessageDecoder ,本节课我们重点分析以下这个抽象类!
一、 解码的原理
ByteToMessageDecoder继承ChannelInboundHandlerAdapter,准确来说,ByteToMessageDecoder的类也可以被当作一个Inbond Handler, 当Netty探测到存在数据后,会传播channRead事件,这一点,我们在前面说的很详细,所以我们重点关注ByteToMessageDecoder#channelRead方法:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//判断是否需要解码 否则直接向下传播
if (msg instanceof ByteBuf) {
//创建一个集合
CodecOutputList out = CodecOutputList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
//判断上一次是否存在未解码的数据
first = cumulation == null;
if (first) {
//没有就直接赋值
cumulation = data;
} else {
//将上一次的数据与这一次的数据合并
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
//开始解码
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
...................忽略不必要代码.....................
int size = out.size();
firedChannelRead |= out.insertSinceRecycled();
//向下传播解码出来的数据
fireChannelRead(ctx, out, size);
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
我们将这个源代码分为三部分来分析:
-
合并数据
首先,程序会判断数据是否是ByteBuf类型的数据,如果不是,那么就证明数据在前面已经被解码了,这里就不需要再进行解码了,而是直接走else进行向下传播。
first = cumulation == null;判断
cumulation这个属性是否为空- 为空:证明上一次没有遗留的未解码的数据
- 不为空:证明上一次的数据没有满足解码条件
if (first) { //没有就直接赋值 cumulation = data; } else { //将上一次的数据与这一次的数据合并 cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data); }如果cumulation属性为空,则不需要其它额外的操作,直接将当前的数据放到 cumulation里面!
如果cumulation属性不为空,需要将cumulation属性里面的数据与本次的ByteBuf里面的数据进行合并,我们进入到合并的源码:
-
private Cumulator cumulator = MERGE_CUMULATOR; -
public static final Cumulator MERGE_CUMULATOR = new Cumulator() { /** * * @param alloc * @param cumulation 上一次的 * @param in 本次的 * @return 合并后的 */ @Override public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) { try { //可读书勋贵 final int required = in.readableBytes(); //判断合并属性是否需要扩容 if (required > cumulation.maxWritableBytes() || (required > cumulation.maxFastWritableBytes() && cumulation.refCnt() > 1) || cumulation.isReadOnly()) { //在以下情况下扩展累积量(通过替换它): // -无法调整累积量的大小以容纳其他数据 // -可以使用重新分配操作扩展累积量以容纳但假定缓冲区是共享的(例如refCnt()> 1),并且重新分配可能并不安全。 return expandCumulation(alloc, cumulation, in); } //直接写 return cumulation.writeBytes(in); } finally { // We must release in in all cases as otherwise it may produce a leak if writeBytes(...) throw // for whatever release (for example because of OutOfMemoryError) in.release(); } } }; -
首先判断本次合并cumulation属性的容量是否够,够的话就直接
cumulation.writeBytes(in);写进去,不够则需要扩容后在合并: -
expandCumulation(alloc, cumulation, in); -
private static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf oldCumulation, ByteBuf in) { //重新分配一个 ByteBuf newCumulation = alloc.buffer(alloc.calculateNewCapacity( oldCumulation.readableBytes() + in.readableBytes(), MAX_VALUE)); ByteBuf toRelease = newCumulation; try { //将上次的 newCumulation.writeBytes(oldCumulation); //本次的 newCumulation.writeBytes(in); toRelease = oldCumulation; return newCumulation; } finally { //释放旧数据的引用计数 toRelease.release(); } } -
首先进行重新分配一个能够承载两批数据的ByteBuf,然后将原先的数据和本次的数据写进新的ByteBuf进行返回!
-
解码数据
//开始解码 callDecode(ctx, cumulation, out);protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { try { while (in.isReadable()) { int outSize = out.size(); //存在已经解码完成的数据 if (outSize > 0) { //向下啊传播过去 fireChannelRead(ctx, out, outSize); //清空 out.clear(); //如果被删除了 就跳过 if (ctx.isRemoved()) { break; } outSize = 0; } //记录一个当前的可读数据 int oldInputLength = in.readableBytes(); //开始调用子类 解码数据 decodeRemovalReentryProtection(ctx, in, out); //如果被删除了 就跳过 if (ctx.isRemoved()) { break; } //true: 没有解析出来数据 //false:解析出来数据了 if (outSize == out.size()) { //没有解析出来数据 就判断之前的可读数据和当前的可读数据是否一致 ,如果一致就证明压根数据就不够,就不处理了 if (oldInputLength == in.readableBytes()) { break; } else { //如果数据被读过 就跳过本次下次接着读 continue; } } //没读取任何东西,但解码了一条消息。 //能走到这 outSize的值必定发生了改变,但是可读字节数却没有发生改变! if (oldInputLength == in.readableBytes()) { throw new DecoderException( StringUtil.simpleClassName(getClass()) + ".decode() did not read anything but decoded a message."); } if (isSingleDecode()) { break; } } } catch (DecoderException e) { throw e; } catch (Exception cause) { throw new DecoderException(cause); } }首先判断 out是否存在数据,我们再前面实现自定义解码器的时候,我们会将解码好的数据放置到 out集合里面,这里的out集合,和那个out集合是一个东西!
如果out存在数据,就证明存在解码完毕,但是没有传播的数据,就先向下传播以下,然后将out集合清空!
然后开始正式解码:
decodeRemovalReentryProtection(ctx, in, out);-
final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { ........忽略......... try { //子类开始解码 decode(ctx, in, out); } finally { ........忽略......... } } -
他调用了一个
decode(ctx, in, out);方法,我们点进去看: -
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception; -
发现他是一个抽象的方法,这个抽象方法就是被各大子类进行实现的解码操作,有关它的讲解,后面会进行分析,这里不做太多讲解!总之子类的这个方法执行完毕之后,如果解码出来了数据,那么out集合一定是存在值的!
-
-
传播数据
我们回到最初的 channelRead方法:
fireChannelRead(ctx, out, size);static void fireChannelRead(ChannelHandlerContext ctx, CodecOutputList msgs, int numElements) { for (int i = 0; i < numElements; i ++) { //循环传播 有多少调用多少 ctx.fireChannelRead(msgs.getUnsafe(i)); } }我们会将out集合里面解码出来的数据,循环每一个都调用传播一次!
二、总结
-
首先这个类会继承 ChannelInboundHandlerAdapter类,再Netty存在数据的向下传播channelRead方法的时候,就会被该解码器的抽象类所捕获!
-
ByteToMessageDecoder捕获到数据后会将数据交给子类进行解码,子类将解码好的数据放到 out集合里面
-
ByteToMessageDecoder会读取 out集合里面解码好的数据,进行向下传播!
之所以单独把这个抽象类拉出来讲解,是因为他对我们后面理解解码器的源码由很大的帮助,他能让我们知道数据是如何跑到我们自定义的解码器的,我们解码后又是如何将数据向下传播的!