`

netty源码分析之FrameDecoder(LengthFieldBasedFrameDecoder)

阅读更多

        我们接下来看一个也是比较重要的的解码器LengthFieldBasedFrameDecoder,这个和DelimiterBasedFrameDecoder比起来没有那么难理解,所以我们简单的看一下。

       和之前一样,我们先来看一下局部变量。

 

    private final int maxFrameLength;
    private final int lengthFieldOffset;
    private final int lengthFieldLength;
    private final int lengthFieldEndOffset;
    private final int lengthAdjustment;
    private final int initialBytesToStrip;
    private final boolean failFast;
    private boolean discardingTooLongFrame;
    private long tooLongFrameLength;
    private long bytesToDiscard;

 

  • maxFrameLength 这个定义最大帧的长度
  • lengthFieldOffset 长度属性的起始指针(偏移量)
  • lengthFieldLength 长度属性的长度,即存放数据包长度的变量的的字节所占的长度
  • lengthFieldEndOffset 这个是一个快捷属性,是根据lengthFieldOffset和lengthFieldLength计算出来的,即就是起始偏移量+长度=结束偏移量
  • lengthAdjustment 这个是一个长度调节值,例如当总长包含头部信息的时候,这个可以是个负数,就比较好实现了
  • initialBytesToStrip 这个属性也比较好理解,就是解码后的数据包需要跳过的头部信息的字节数
  • failFast 这个和DelimiterBasedFrameDecoder是一致的,就是如果设置成true,当发现解析的数据超过maxFrameLenght就立马报错,否则当整个帧的数据解析完后才报错
  • discardingTooLongFrame 这个也是一个导出属性,就是当前编码器的状态,是不是处于丢弃超长帧的状态
  • tooLongFrameLength 这个是当出现超长帧的时候,这个超长帧的长度
  • bytesToDiscard 这个来定义,当出现超长帧的时候,丢弃的数据的字节数

  接下来我们就深入主题,来看这个类的实现,最后我们再分析下javadoc,这样我们就能够彻底的掌握这个编码器了。

      

 if (discardingTooLongFrame) {
            long bytesToDiscard = this.bytesToDiscard;
            int localBytesToDiscard = (int) Math.min(bytesToDiscard, buffer.readableBytes());
            buffer.skipBytes(localBytesToDiscard);
            bytesToDiscard -= localBytesToDiscard;
            this.bytesToDiscard = bytesToDiscard;
            failIfNecessary(ctx, false);
            return null;
        }

        if (buffer.readableBytes() < lengthFieldEndOffset) {
            return null;
        }

        这个逻辑是判断,如果当前的编码器处于丢弃超长帧的状态,这个状态肯定是上传编码的时候被设置成这个状态的,然后取上次丢弃的字节数和当前buffer里面的可读数据的最小值,后面的程序理解起来不是那么直接,这个bytesToDiscard属性主要是在后面设置的,bytesToDiscard = frameLength - buffer.readableBytes(); frameLength是当前超长帧的字节数,buffer.readableBytes是当前buffer里面的数据的字节数,所以它的含义就是告诉编码器下次还要丢弃的字节数,举个例子,最大帧的长度是1000,当前帧的长度是1024,所以超过了最大帧了,比如当前buffer里面长度是18个字节,所以bytesToDiscard就是1024-18 = 1006,含义就是说下次解码的时候还要丢弃1006个字节,那上面的代码就比较好理解了,就是说在计算下一次编码需要丢弃的字节数。后面是就是调用failIfNecessary函数了。

       我们先来看一下它的实现吧:

      

 private void failIfNecessary(ChannelHandlerContext ctx, boolean firstDetectionOfTooLongFrame) {
        if (bytesToDiscard == 0) {
            // Reset to the initial state and tell the handlers that
            // the frame was too large.
            long tooLongFrameLength = this.tooLongFrameLength;
            this.tooLongFrameLength = 0;
            discardingTooLongFrame = false;
            if ((!failFast) ||
                (failFast && firstDetectionOfTooLongFrame))
            {
                fail(ctx, tooLongFrameLength);
            }
        } else {
            // Keep discarding and notify handlers if necessary.
            if (failFast && firstDetectionOfTooLongFrame)
            {
                fail(ctx, this.tooLongFrameLength);
            }
        }

    }

        如果bytesToDiscard是0,就是说下次编码的时候不需要丢弃了,说明这个超长帧读取完毕,那么将这个编码器的状态设置为非丢弃超长帧状态,如果不是failFast状态或者是failFast又是第一次出现超长帧,就报错,意思其实挺明白,如果不是failFast状态,在这个超长帧读取完毕后理应抛出异常,如果是failFast是第一次发现了超长帧,所以也要抛出异常。else分支很好理解,就是说发现了超长帧,并且客户配置的是立马抛出异常,我们就直接抛出异常即可。

      我们接着来看decode的代码实现:

           

  if (buffer.readableBytes() < lengthFieldEndOffset) {
            return null;
        }

        int actualLengthFieldOffset = buffer.readerIndex() + lengthFieldOffset;
        long frameLength;
        switch (lengthFieldLength) {
        case 1:
            frameLength = buffer.getUnsignedByte(actualLengthFieldOffset);
            break;
        case 2:
            frameLength = buffer.getUnsignedShort(actualLengthFieldOffset);
            break;
        case 3:
            frameLength = buffer.getUnsignedMedium(actualLengthFieldOffset);
            break;
        case 4:
            frameLength = buffer.getUnsignedInt(actualLengthFieldOffset);
            break;
        case 8:
            frameLength = buffer.getLong(actualLengthFieldOffset);
            break;
        default:
            throw new Error("should not reach here");
        }

       这段代码结构性很好,也很好理解,如果发现可读的字节数不够,就返回null,等待下次messageReceived通知,然后根据length的长度进行读操作。

      我们接着超下看:

if (frameLength < 0) {
            buffer.skipBytes(lengthFieldEndOffset);
            throw new CorruptedFrameException(
                    "negative pre-adjustment length field: " + frameLength);
        }

        frameLength += lengthAdjustment + lengthFieldEndOffset;
        if (frameLength < lengthFieldEndOffset) {
            buffer.skipBytes(lengthFieldEndOffset);
            throw new CorruptedFrameException(
                    "Adjusted frame length (" + frameLength + ") is less " +
                    "than lengthFieldEndOffset: " + lengthFieldEndOffset);
        }
  •  如果我们发现读到的长度是负值,我们将read指针重新设置成读之前的位置,并抛出异常。
  • 计算帧的长度,这个里面是lengthAdjustment + lengthFieldEndOffset,这个比较好理解,这个里面同样做了判断,就是当前帧的长度,不能比lenghtFieldEndOffset小,含义就是帧的数据内容可能为0,但是不可能为负
 if (frameLength > maxFrameLength) {
            // Enter the discard mode and discard everything received so far.
            discardingTooLongFrame = true;
            tooLongFrameLength = frameLength;
            bytesToDiscard = frameLength - buffer.readableBytes();
            buffer.skipBytes(buffer.readableBytes());
            failIfNecessary(ctx, true);
            return null;
        }

        // never overflows because it's less than maxFrameLength
        int frameLengthInt = (int) frameLength;
        if (buffer.readableBytes() < frameLengthInt) {
            return null;
        }

        这个代码就是在处理超长帧的问题,这个在上面我们已经进行解析了

  • 如果发现当前帧的长度超过超长帧的定义,我们将当前的解码器标记为丢弃超长帧状态
  • 计算下一次编码还需要丢弃的长度
  • 修改buffer的读指针
  • 调用failIfNecessary这个函数,判断是否需要抛出异常,关于failIfNecessary函数我们上面已经讲了
  • 然后返回null标记此次解码失败,等待下次messageReceived通知
  • 然后做一次判断,看当前可读数据是不是够一个int型的字节,因为现在马上要读取数据帧的长度了
  if (initialBytesToStrip > frameLengthInt) {
            buffer.skipBytes(frameLengthInt);
            throw new CorruptedFrameException(
                    "Adjusted frame length (" + frameLength + ") is less " +
                    "than initialBytesToStrip: " + initialBytesToStrip);
        }
        buffer.skipBytes(initialBytesToStrip);

        // extract frame
        int readerIndex = buffer.readerIndex();
        int actualFrameLength = frameLengthInt - initialBytesToStrip;
        ChannelBuffer frame = extractFrame(buffer, readerIndex, actualFrameLength);
        buffer.readerIndex(readerIndex + actualFrameLength);
        return frame;

         这个是decode的最后的实现了

  • 最开始是做了一个判断,就是起始的跳过字节数的变量太大,也要抛出异常的
  • 计算数据帧的长度,就是数据包头部里面定义的长度减去需要跳过的字节数
  • 最后就是提取数据并修改读指针,最后返回数据帧

          我们最后来看下这个类的javadoc吧,我感觉netty的作者作为和我们同年龄的80后青年,不但代码写的好,注释也写的很好,这种分享精神很值得我们学习。

 

  •    第一种情况:从lenght从头开始,长度为2,不跳过头部

                  

lengthFieldOffset   = 0
 lengthFieldLength   = 2
 lengthAdjustment    = 0
 initialBytesToStrip = 0 (= do not strip header)

 BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
 +--------+----------------+      +--------+----------------+
 | Length | Actual Content |----->| Length | Actual Content |
 | 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
 +--------+----------------+      +--------+----------------+

  •  第二种情况,从length从头开始,长度为2,跳过头部
 lengthFieldOffset   = 0
 lengthFieldLength   = 2
 lengthAdjustment    = 0
 initialBytesToStrip = 2 (= the length of the Length field)

 BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
 +--------+----------------+      +----------------+
 | Length | Actual Content |----->| Actual Content |
 | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
 +--------+----------------+      +----------------+

     这个和上面的区别是,数据经过解码后头部被去掉了

 

  • 第三种情况,2个字节长度的头部,offset是0,但是长度代表的是整个数据帧的长度,即就是包含头部
lengthFieldOffset   =  0
 lengthFieldLength   =  2
 lengthAdjustment    = -2 (= the length of the Length field)
 initialBytesToStrip =  0

 BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
 +--------+----------------+      +--------+----------------+
 | Length | Actual Content |----->| Length | Actual Content |
 | 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
 +--------+----------------+      +--------+----------------+

 

  • 第四种情况,5个字节长度的头部,但是数据帧的lenght的字节数是3,offset是2,不跳过头部
 lengthFieldOffset   = 2 (= the length of Header 1)
 lengthFieldLength   = 3
 lengthAdjustment    = 0
 initialBytesToStrip = 0

 BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
 +----------+----------+----------------+      +----------+----------+----------------+
 | Header 1 |  Length  | Actual Content |----->| Header 1 |  Length  | Actual Content |
 |  0xCAFE  | 0x00000C | "HELLO, WORLD" |      |  0xCAFE  | 0x00000C | "HELLO, WORLD" |
 +----------+----------+----------------+      +----------+----------+----------------+

 

 

  • 第五种情况,5个字节长度的头部,但是数据帧的lenght的字节数是3,offset是0,不跳过头部

 

lengthFieldOffset   = 0
 lengthFieldLength   = 3
 lengthAdjustment    = 2 (= the length of Header 1)
 initialBytesToStrip = 0

 BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
 +----------+----------+----------------+      +----------+----------+----------------+
 |  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
 | 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
 +----------+----------+----------------+      +----------+----------+----------------+

    这个稍微解释一下,由于我们的头部是5个字节,但是length数据域只有3个字节,所以lengthAdjustment是2,表明需要2个字节来调整整个帧的长度。

 

  • 第六种情况,4个字节长度的头部,但是数据帧的lenght的字节数是2,offset是1,跳过头部第一个数据域和长度域
lengthFieldOffset   = 1 (= the length of HDR1)
 lengthFieldLength   = 2
 lengthAdjustment    = 1 (= the length of HDR2)
 initialBytesToStrip = 3 (= the length of HDR1 + LEN)

 BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
 +------+--------+------+----------------+      +------+----------------+
 | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
 | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
 +------+--------+------+----------------+      +------+----------------+

         这个我稍微解释一下,HDR1是一个字节,Lenght是2个字节,HDR2是两个字节,解码后需要包含HDR2,所以lenghtFieldOffset是1,lenghtFieldLenght是2,lenghtAdjustment是1,实际上就是HDR2的长度,initailBytesToStrip是3,实际上就是HDR1+LEN的长度。

 

  • 第七种情况,4个字节长度的头部,但是数据帧的lenght的字节数是2,offset是1,跳过头部第一个数据域和长度域,消息的长度代表整个数据帧的长度
lengthFieldOffset   =  1
 lengthFieldLength   =  2
 lengthAdjustment    = -3 (= the length of HDR1 + LEN, negative)
 initialBytesToStrip =  3

 BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
 +------+--------+------+----------------+      +------+----------------+
 | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
 | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |

   这个我稍微解释一下,HDR1是一个字节,Lenght是2个字节,HDR2是两个字节,解码后需要包含HDR2,所以lenghtFieldOffset是1,lenghtFieldLenght是2,lenghtAdjustment是-3,实际上就是HDR1+LEN的长度,initailBytesToStrip是3,实际上就是HDR1+LEN的长度。

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics