LengthFieldBasedFrameDecoder

综述


A decoder that splits the received {@link ByteBuf}s dynamically by the
value of the length field in the message.  It is particularly useful when you
decode a binary message which has an integer header field that represents the
length of the message body or the whole message.
<p>
{@link LengthFieldBasedFrameDecoder} has many configuration parameters so
that it can decode any message with a length field, which is often seen in
proprietary client-server protocols. Here are some example that will give
you the basic idea on which option does what.

<h3>2 bytes length field at offset 0, do not strip header</h3>

The value of the length field in this example is <tt>12 (0x0C)</tt> which
represents the length of "HELLO, WORLD".  By default, the decoder assumes
that the length field represents the number of the bytes that follows the
length field.  Therefore, it can be decoded with the simplistic parameter
combination.
<pre>
<b>lengthFieldOffset</b>   = <b>0</b>
<b>lengthFieldLength</b>   = <b>2</b>
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" |
+--------+----------------+      +--------+----------------+
</pre>

<h3>2 bytes length field at offset 0, strip header</h3>

Because we can get the length of the content by calling
{@link ByteBuf#readableBytes()}, you might want to strip the length
field by specifying <tt>initialBytesToStrip</tt>.  In this example, we
specified <tt>2</tt>, that is same with the length of the length field, to
strip the first two bytes.
<pre>
lengthFieldOffset   = 0
lengthFieldLength   = 2
lengthAdjustment    = 0
<b>initialBytesToStrip</b> = <b>2</b> (= the length of the Length field)

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

<h3>2 bytes length field at offset 0, do not strip header, the length field
    represents the length of the whole message</h3>

In most cases, the length field represents the length of the message body
only, as shown in the previous examples.  However, in some protocols, the
length field represents the length of the whole message, including the
message header.  In such a case, we specify a non-zero
<tt>lengthAdjustment</tt>.  Because the length value in this example message
is always greater than the body length by <tt>2</tt>, we specify <tt>-2</tt>
as <tt>lengthAdjustment</tt> for compensation.
<pre>
lengthFieldOffset   =  0
lengthFieldLength   =  2
<b>lengthAdjustment</b>    = <b>-2</b> (= 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" |
+--------+----------------+      +--------+----------------+
</pre>

<h3>3 bytes length field at the end of 5 bytes header, do not strip header</h3>

The following message is a simple variation of the first example.  An extra
header value is prepended to the message.  <tt>lengthAdjustment</tt> is zero
again because the decoder always takes the length of the prepended data into
account during frame length calculation.
<pre>
<b>lengthFieldOffset</b>   = <b>2</b> (= the length of Header 1)
<b>lengthFieldLength</b>   = <b>3</b>
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" |
+----------+----------+----------------+      +----------+----------+----------------+
</pre>

<h3>3 bytes length field at the beginning of 5 bytes header, do not strip header</h3>

This is an advanced example that shows the case where there is an extra
header between the length field and the message body.  You have to specify a
positive <tt>lengthAdjustment</tt> so that the decoder counts the extra
header into the frame length calculation.
<pre>
lengthFieldOffset   = 0
lengthFieldLength   = 3
<b>lengthAdjustment</b>    = <b>2</b> (= 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" |
+----------+----------+----------------+      +----------+----------+----------------+
</pre>

<h3>2 bytes length field at offset 1 in the middle of 4 bytes header,
    strip the first header field and the length field</h3>

This is a combination of all the examples above.  There are the prepended
header before the length field and the extra header after the length field.
The prepended header affects the <tt>lengthFieldOffset</tt> and the extra
header affects the <tt>lengthAdjustment</tt>.  We also specified a non-zero
<tt>initialBytesToStrip</tt> to strip the length field and the prepended
header from the frame.  If you don't want to strip the prepended header, you
could specify <tt>0</tt> for <tt>initialBytesToSkip</tt>.
<pre>
lengthFieldOffset   = 1 (= the length of HDR1)
lengthFieldLength   = 2
<b>lengthAdjustment</b>    = <b>1</b> (= the length of HDR2)
<b>initialBytesToStrip</b> = <b>3</b> (= 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" |
+------+--------+------+----------------+      +------+----------------+
</pre>

<h3>2 bytes length field at offset 1 in the middle of 4 bytes header,
    strip the first header field and the length field, the length field
    represents the length of the whole message</h3>

Let's give another twist to the previous example.  The only difference from
the previous example is that the length field represents the length of the
whole message instead of the message body, just like the third example.
We have to count the length of HDR1 and Length into <tt>lengthAdjustment</tt>.
Please note that we don't need to take the length of HDR2 into account
because the length field already includes the whole header length.
<pre>
lengthFieldOffset   =  1
lengthFieldLength   =  2
<b>lengthAdjustment</b>    = <b>-3</b> (= the length of HDR1 + LEN, negative)
<b>initialBytesToStrip</b> = <b> 3</b>

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

关键属性

private final ByteOrder byteOrder;
// 消息最大长度
private final int maxFrameLength;
// 长度域的offset
private final int lengthFieldOffset;
// 长度域自身占用的长度
private final int lengthFieldLength;
// 长度域的结束offset, lengthFieldOffset + 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;

解码

protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
   // 如果处于丢弃模式
   if (discardingTooLongFrame) {
        // 拿到当前还剩多少要丢弃
        long bytesToDiscard = this.bytesToDiscard;
        // 看是不是到头了,如果不是跳过这批数据
        int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
        in.skipBytes(localBytesToDiscard);
        // 继续计算还剩多少要丢弃
        bytesToDiscard -= localBytesToDiscard;
        this.bytesToDiscard = bytesToDiscard;

        failIfNecessary(false);
    }
    
    // 如果当前可读的字节如果小于长度域,那么也没有继续下去的必要了。
    if (in.readableBytes() < lengthFieldEndOffset) {
        return null;
    }
    
    // 拿到实际长度域的起始index
    int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
    // 解析长度域中实际长度
    long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);

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

    // 整个消息的长度当然是等于实际长度+长度域长度+lengthAdjustment
    frameLength += lengthAdjustment + lengthFieldEndOffset;

    // 如果整个消息的长度还小于长度域结尾,只有一种可能,就是lengthAdjustment设置得不对
    if (frameLength < lengthFieldEndOffset) {
        in.skipBytes(lengthFieldEndOffset);
        throw new CorruptedFrameException(
                "Adjusted frame length (" + frameLength + ") is less " +
                "than lengthFieldEndOffset: " + lengthFieldEndOffset);
    }

    // 如果长度大于消息最大长度
    if (frameLength > maxFrameLength) {
        // 还剩多少要丢弃
        long discard = frameLength - in.readableBytes();
        // 记录总的丢弃字节数
        tooLongFrameLength = frameLength;

        // 说明消息的总长要小于可读字节数,那么直接跳过当前消息,准备开始读取下一条消息
        if (discard < 0) {            
            in.skipBytes((int) frameLength);
        } else {
            // 否则消息的总长要大于或等于可读字节数,
            // 那么进入丢弃模式,并直接跳过这一批数据
            // Enter the discard mode and discard everything received so far.
            discardingTooLongFrame = true;
            // 记录还需要丢弃的量
            bytesToDiscard = discard;
            in.skipBytes(in.readableBytes());
        }
        // 是否需要抛出异常
        failIfNecessary(true);
        return null;
    }
   
    // 如果当前可读的字节还不到我期望的消息长度的时候,什么都不做,直接返回null
    // 等待下一次处理
    int frameLengthInt = (int) frameLength;
    if (in.readableBytes() < frameLengthInt) {
        return null;
    }

    // 如果初始跳过的字节数大于消息长度,那么说明initialBytesToStrip设置得有问题
    if (initialBytesToStrip > frameLengthInt) {
        in.skipBytes(frameLengthInt);
        throw new CorruptedFrameException(
                "Adjusted frame length (" + frameLength + ") is less " +
                "than initialBytesToStrip: " + initialBytesToStrip);
    }
    // 否则,先跳过initialBytesToStrip
    in.skipBytes(initialBytesToStrip);

    // extract frame
    int readerIndex = in.readerIndex();
    // 拿到跳过initialBytesToStrip后的消息长度
    int actualFrameLength = frameLengthInt - initialBytesToStrip;
    // 解析数据从readerindex开始,读取actualFrameLength个字节
    // 返回retainedslice给out容器
    ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
    // 将readerindex移动到消息尾部,准备下次读取
    in.readerIndex(readerIndex + actualFrameLength);
    return frame;
}

failIfNecessary

private void failIfNecessary(boolean firstDetectionOfTooLongFrame) {
    // 如果期望丢弃的数据已经处理完毕
    if (bytesToDiscard == 0) {
        // 重置tooLongFrameLength为0,退出丢弃模式
        long tooLongFrameLength = this.tooLongFrameLength;
        this.tooLongFrameLength = 0;
        discardingTooLongFrame = false;
        // 是否需要抛出异常
        if (!failFast ||
            failFast && firstDetectionOfTooLongFrame) {
            fail(tooLongFrameLength);
        }
    } else {
        // 是否首次就抛出异常
        // Keep discarding and notify handlers if necessary.
        if (failFast && firstDetectionOfTooLongFrame) {
            fail(tooLongFrameLength);
        }
    }
}

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容