1. 程式人生 > >Netty原始碼分析 (十)----- 拆包器之LineBasedFrameDecoder

Netty原始碼分析 (十)----- 拆包器之LineBasedFrameDecoder

Netty 自帶多個粘包拆包解碼器。今天介紹 LineBasedFrameDecoder,換行符解碼器。

行拆包器

下面,以一個具體的例子來看看業netty自帶的拆包器是如何來拆包的

這個類叫做 LineBasedFrameDecoder,基於行分隔符的拆包器,TA可以同時處理 \n以及\r\n兩種型別的行分隔符,核心方法都在繼承的 decode 方法中

protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    Object decoded = decode(ctx, in);
    if (decoded != null) {
        out.add(decoded);
    }
}

netty 中自帶的拆包器都是如上這種模板,我們來看看decode(ctx, in);

protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
    int eol = findEndOfLine(buffer);
    int length;
    int length;
    if (!this.discarding) {
        if (eol >= 0) {
            length = eol - buffer.readerIndex();
            int delimLength = buffer.getByte(eol) == '\r' ? 2 : 1;
            if (length > this.maxLength) {
                buffer.readerIndex(eol + delimLength);
                this.fail(ctx, length);
                return null;
            } else {
                ByteBuf frame;
                if (this.stripDelimiter) {
                    frame = buffer.readRetainedSlice(length);
                    buffer.skipBytes(delimLength);
                } else {
                    frame = buffer.readRetainedSlice(length + delimLength);
                }

                return frame;
            }
        } else {
            length = buffer.readableBytes();
            if (length > this.maxLength) {
                this.discardedBytes = length;
                buffer.readerIndex(buffer.writerIndex());
                this.discarding = true;
                if (this.failFast) {
                    this.fail(ctx, "over " + this.discardedBytes);
                }
            }

            return null;
        }
    } else {
        if (eol >= 0) {
            length = this.discardedBytes + eol - buffer.readerIndex();
            length = buffer.getByte(eol) == '\r' ? 2 : 1;
            buffer.readerIndex(eol + length);
            this.discardedBytes = 0;
            this.discarding = false;
            if (!this.failFast) {
                this.fail(ctx, length);
            }
        } else {
            this.discardedBytes += buffer.readableBytes();
            buffer.readerIndex(buffer.writerIndex());
        }

        return null;
    }
}

ByteProcessor FIND_LF = new IndexOfProcessor((byte) '\n');

private static int findEndOfLine(ByteBuf buffer) {
    int i = buffer.forEachByte(ByteProcessor.FIND_LF);
    if (i > 0 && buffer.getByte(i - 1) == '\r') {
        --i;
    }

    return i;
}

找到換行符位置

final int eol = findEndOfLine(buffer);

private static int findEndOfLine(final ByteBuf buffer) {
    int i = buffer.forEachByte(ByteProcessor.FIND_LF);
    if (i > 0 && buffer.getByte(i - 1) == '\r') {
        i--;
    }
    return i;
}

ByteProcessor FIND_LF = new IndexOfProcessor((byte) '\n');

for迴圈遍歷,找到第一個 \n 的位置,如果\n前面的字元為\r,那就返回\r的位置

非discarding模式的處理

接下來,netty會判斷,當前拆包是否屬於丟棄模式,用一個成員變數來標識

private boolean discarding;

第一次拆包不在discarding模式

非discarding模式下找到行分隔符的處理

// 1.計算分隔符和包長度
final ByteBuf frame;
final int length = eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;

// 丟棄異常資料
if (length > maxLength) {
    buffer.readerIndex(eol + delimLength);
    fail(ctx, length);
    return null;
}

// 取包的時候是否包括分隔符
if (stripDelimiter) {
    frame = buffer.readRetainedSlice(length);
    buffer.skipBytes(delimLength);
} else {
    frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;

1.首先,新建一個幀,計算一下當前包的長度和分隔符的長度(因為有兩種分隔符)
2.然後判斷一下需要拆包的長度是否大於該拆包器允許的最大長度(maxLength),這個引數在建構函式中被傳遞進來,如超出允許的最大長度,就將這段資料拋棄,返回null
3.最後,將一個完整的資料包取出,如果構造本解包器的時候指定 stripDelimiter為false,即解析出來的包包含分隔符,預設為不包含分隔符

非discarding模式下未找到分隔符的處理

沒有找到對應的行分隔符,說明位元組容器沒有足夠的資料拼接成一個完整的業務資料包,進入如下流程處理

final int length = buffer.readableBytes();
if (length > maxLength) {
    discardedBytes = length;
    buffer.readerIndex(buffer.writerIndex());
    discarding = true;
    if (failFast) {
        fail(ctx, "over " + discardedBytes);
    }
}
return null;

首先取得當前位元組容器的可讀位元組個數,接著,判斷一下是否已經超過可允許的最大長度,如果沒有超過,直接返回null,位元組容器中的資料沒有任何改變,否則,就需要進入丟棄模式

使用一個成員變數 discardedBytes 來表示已經丟棄了多少資料,然後將位元組容器的讀指標移到寫指標,意味著丟棄這一部分資料,設定成員變數discarding為true表示當前處於丟棄模式。如果設定了failFast,那麼直接丟擲異常,預設情況下failFast為false,即安靜得丟棄資料

discarding模式

如果解包的時候處在discarding模式,也會有兩種情況發生

discarding模式下找到行分隔符

在discarding模式下,如果找到分隔符,那可以將分隔符之前的都丟棄掉

final int length = discardedBytes + eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
buffer.readerIndex(eol + delimLength);
discardedBytes = 0;
discarding = false;
if (!failFast) {
    fail(ctx, length);
}

計算出分隔符的長度之後,直接把分隔符之前的資料全部丟棄,當然丟棄的字元也包括分隔符,經過這麼一次丟棄,後面就有可能是正常的資料包,下一次解包的時候就會進入正常的解包流程

discarding模式下未找到行分隔符

這種情況比較簡單,因為當前還在丟棄模式,沒有找到行分隔符意味著當前一個完整的資料包還沒丟棄完,當前讀取的資料是丟棄的一部分,所以直接丟棄

discardedBytes += buffer.readableBytes();
buffer.readerIndex(buffer.writerIndex());

特定分隔符拆包

這個類叫做 DelimiterBasedFrameDecoder,可以傳遞給TA一個分隔符列表,資料包會按照分隔符列表進行拆分,讀者可以完全根據行拆包器的思路去分析這個DelimiterBasedFrameDecoder

&n