抽象接口ByteToMessageDecoder源码解析| 8月更文挑战

542 阅读6分钟

上一节课我们将常用的解码器包括行解码器、自定义分隔符解码器、定长解码器、不定长解码器、自定义解码器的基本使用,本节课我们分析下这几个解码器的源码!

我们看一下上述解码器的继承关系:

解码器继承图

我们发现无论是内置解码器还是我们自定义解码器,都必定会继承一个抽象类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);
    }
}

我们将这个源代码分为三部分来分析:

  1. 合并数据

    首先,程序会判断数据是否是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进行返回!

  2. 解码数据

    //开始解码
    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集合一定是存在值的!

  3. 传播数据

    我们回到最初的 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集合里面解码出来的数据,循环每一个都调用传播一次!

二、总结

  1. 首先这个类会继承 ChannelInboundHandlerAdapter类,再Netty存在数据的向下传播channelRead方法的时候,就会被该解码器的抽象类所捕获!

  2. ByteToMessageDecoder捕获到数据后会将数据交给子类进行解码,子类将解码好的数据放到 out集合里面

  3. ByteToMessageDecoder会读取 out集合里面解码好的数据,进行向下传播!

之所以单独把这个抽象类拉出来讲解,是因为他对我们后面理解解码器的源码由很大的帮助,他能让我们知道数据是如何跑到我们自定义的解码器的,我们解码后又是如何将数据向下传播的!