1. 程式人生 > >Netty原始碼分析第6章(解碼器)---->第4節: 分隔符解碼器

Netty原始碼分析第6章(解碼器)---->第4節: 分隔符解碼器

 

Netty原始碼分析第六章: 解碼器

 

第四節: 分隔符解碼器

 

 

基於分隔符解碼器DelimiterBasedFrameDecoder, 是按照指定分隔符進行解碼的解碼器, 通過分隔符, 可以將二進位制流拆分成完整的資料包

 

同樣繼承了ByteToMessageDecoder並重寫了decode方法

 

我們看其中的一個構造方法:

public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters) {
    
this(maxFrameLength, true, delimiters); }

這裡引數maxFrameLength代表最大長度, delimiters是個可變引數, 可以說可以支援多個分隔符進行解碼

我們進入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);
    }
}

這裡同樣呼叫了其過載的decode方法並將解析好的資料新增到集合list中, 其父類就可以遍歷out, 並將內容傳播

我們跟到過載decode方法中:

protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
    //行處理器(1)
    if (lineBasedDecoder != null) { 
        return lineBasedDecoder.decode(ctx, buffer);
    }
    int minFrameLength = Integer.MAX_VALUE;
    ByteBuf minDelim 
= null; //找到最小長度的分隔符(2) for (ByteBuf delim: delimiters) { //每個分隔符分隔的資料包長度 int frameLength = indexOf(buffer, delim); if (frameLength >= 0 && frameLength < minFrameLength) { minFrameLength = frameLength; minDelim = delim; } } //解碼(3) //已經找到分隔符 if (minDelim != null) { int minDelimLength = minDelim.capacity(); ByteBuf frame; //當前分隔符否處於丟棄模式 if (discardingTooLongFrame) { //首先設定為非丟棄模式 discardingTooLongFrame = false; //丟棄 buffer.skipBytes(minFrameLength + minDelimLength); int tooLongFrameLength = this.tooLongFrameLength; this.tooLongFrameLength = 0; if (!failFast) { fail(tooLongFrameLength); } return null; } //處於非丟棄模式 //當前找到的資料包, 大於允許的資料包 if (minFrameLength > maxFrameLength) { //當前資料包+最小分隔符長度 全部丟棄 buffer.skipBytes(minFrameLength + minDelimLength); //傳遞異常事件 fail(minFrameLength); return null; } //如果是正常的長度 //解析出來的資料包是否忽略分隔符 if (stripDelimiter) { //如果不包含分隔符 //擷取 frame = buffer.readRetainedSlice(minFrameLength); //跳過分隔符 buffer.skipBytes(minDelimLength); } else { //擷取包含分隔符的長度 frame = buffer.readRetainedSlice(minFrameLength + minDelimLength); } return frame; } else { //如果沒有找到分隔符 //非丟棄模式 if (!discardingTooLongFrame) { //可讀位元組大於允許的解析出來的長度 if (buffer.readableBytes() > maxFrameLength) { //將這個長度記錄下 tooLongFrameLength = buffer.readableBytes(); //跳過這段長度 buffer.skipBytes(buffer.readableBytes()); //標記當前處於丟棄狀態 discardingTooLongFrame = true; if (failFast) { fail(tooLongFrameLength); } } } else { tooLongFrameLength += buffer.readableBytes(); buffer.skipBytes(buffer.readableBytes()); } return null; } }

這裡的方法也比較長, 這裡也通過拆分進行剖析

(1). 行處理器

(2). 找到最小長度分隔符

(3). 解碼

首先看第一步行處理器:

 

if (lineBasedDecoder != null) { 
    return lineBasedDecoder.decode(ctx, buffer);
}

這裡首先判斷成員變數lineBasedDecoder是否為空, 如果不為空則直接呼叫lineBasedDecoder的decode的方法進行解碼, lineBasedDecoder實際上就是上一小節剖析的LineBasedFrameDecoder解碼器

這個成員變數, 會在分隔符是\n和\r\n的時候進行初始化

我們看初始化該屬性的構造方法:

public DelimiterBasedFrameDecoder(
        int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) {
    //程式碼省略
    //如果是基於行的分隔
    if (isLineBased(delimiters) && !isSubclass()) {
        //初始化行處理器
        lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
        this.delimiters = null;
    } else {
        //程式碼省略
    }
    //程式碼省略
}

這裡isLineBased(delimiters)會判斷是否是基於行的分隔, 跟到isLineBased方法中:

private static boolean isLineBased(final ByteBuf[] delimiters) {
    //分隔符長度不為2
    if (delimiters.length != 2) {
        return false;
    }
    //拿到第一個分隔符
    ByteBuf a = delimiters[0];
    //拿到第二個分隔符
    ByteBuf b = delimiters[1];
    if (a.capacity() < b.capacity()) {
        a = delimiters[1];
        b = delimiters[0];
    }
    //確保a是/r/n分隔符, 確保b是/n分隔符
    return a.capacity() == 2 && b.capacity() == 1
            && a.getByte(0) == '\r' && a.getByte(1) == '\n'
            && b.getByte(0) == '\n';
}

首先判斷長度等於2, 直接返回false

然後拿到第一個分隔符a和第二個分隔符b, 然後判斷a的第一個分隔符是不是\r, a的第二個分隔符是不是\n, b的第一個分隔符是不是\n, 如果都為true, 則條件成立

我們回到decode方法中, 看步驟2, 找到最小長度的分隔符:

這裡最小長度的分隔符, 意思就是從讀指標開始, 找到最近的分隔符

for (ByteBuf delim: delimiters) {
    //每個分隔符分隔的資料包長度
    int frameLength = indexOf(buffer, delim);
    if (frameLength >= 0 && frameLength < minFrameLength) {
        minFrameLength = frameLength;
        minDelim = delim;
    }
}

這裡會遍歷所有的分隔符, 然後找到每個分隔符到讀指標到資料包長度

然後通過if判斷, 找到長度最小的資料包的長度, 然後儲存當前資料包的的分隔符, 如下圖:

6-4-1

這裡假設A和B同為分隔符, A分隔符到讀指標的長度小於B分隔符到讀指標的長度, 這裡會找到最小的分隔符A, 分隔符的最小長度, 就readIndex到A的長度

我們繼續看第3步, 解碼:

 if (minDelim != null) 表示已經找到最小長度分隔符, 我們繼續看if塊中的邏輯:

int minDelimLength = minDelim.capacity();
ByteBuf frame; 
if (discardingTooLongFrame) { 
    discardingTooLongFrame = false; 
    buffer.skipBytes(minFrameLength + minDelimLength); 
    int tooLongFrameLength = this.tooLongFrameLength;
    this.tooLongFrameLength = 0;
    if (!failFast) {
        fail(tooLongFrameLength);
    }
    return null;
} 
if (minFrameLength > maxFrameLength) { 
    buffer.skipBytes(minFrameLength + minDelimLength); 
    fail(minFrameLength);
    return null;
} 
if (stripDelimiter) { 
    frame = buffer.readRetainedSlice(minFrameLength); 
    buffer.skipBytes(minDelimLength);
} else { 
    frame = buffer.readRetainedSlice(minFrameLength + minDelimLength);
}
return frame;

 if (discardingTooLongFrame) 表示當前是否處於非丟棄模式, 如果是丟棄模式, 則進入if

因為第一個不是丟棄模式, 所以這裡先分析if塊後面的邏輯

 if (minFrameLength > maxFrameLength) 這裡是判斷當前找到的資料包長度大於最大長度, 這裡的最大長度使我們建立解碼器的時候設定的, 如果超過了最大長度, 就通過 buffer.skipBytes(minFrameLength + minDelimLength) 方式, 跳過資料包+分隔符的長度, 也就是將這部分資料進行完全丟棄

繼續往下看, 如果長度不大最大允許長度, 則通過 if (stripDelimiter) 判斷解析的出來的資料包是否包含分隔符, 如果不包含分隔符, 則擷取資料包的長度之後, 跳過分隔符

 

我們再回頭看 if (discardingTooLongFrame) 中的if塊中的邏輯, 也就是丟棄模式:

首先將discardingTooLongFrame設定為false, 標記非丟棄模式

然後通過 buffer.skipBytes(minFrameLength + minDelimLength) 將資料包+分隔符長度的位元組數跳過, 也就是進行丟棄, 之後再進行丟擲異常

 

 

 

分析完成了找到分隔符之後的丟棄模式非丟棄模式的邏輯處理, 我們在分析沒找到分隔符的邏輯處理, 也就是 if (minDelim != null) 中的else:

if (!discardingTooLongFrame) { 
    if (buffer.readableBytes() > maxFrameLength) { 
        tooLongFrameLength = buffer.readableBytes();
        buffer.skipBytes(buffer.readableBytes());
        discardingTooLongFrame = true;
        if (failFast) {
            fail(tooLongFrameLength);
        }
    }
} else {
    tooLongFrameLength += buffer.readableBytes();
    buffer.skipBytes(buffer.readableBytes());
}
return null;

首先通過 if (!discardingTooLongFrame) 判斷是否為非丟棄模式, 如果是, 則進入if:

if塊中, 首先通過 if (buffer.readableBytes() > maxFrameLength) 判斷當前可讀位元組數是否大於最大允許的長度, 如果大於最大允許的長度, 則將可讀位元組數設定到tooLongFrameLength的屬性中, 代表丟棄的位元組數

然後通過 buffer.skipBytes(buffer.readableBytes()) 將累計器中所有的可讀位元組進行丟棄

最後將discardingTooLongFrame設定為true, 也就是丟棄模式, 之後丟擲異常

 

如果 if (!discardingTooLongFrame) 為false, 也就是當前處於丟棄模式, 則追加tooLongFrameLength也就是丟棄的位元組數的長度, 並通過 buffer.skipBytes(buffer.readableBytes()) 將所有的位元組繼續進行丟棄

 

以上就是分隔符解碼器的相關邏輯

 

 

第六章總結

 

        本章介紹了抽象解碼器ByteToMessageDecoder, 和其他幾個實現了ByteToMessageDecoder類的解碼器, 這個幾個解碼器邏輯都比較簡單, 同學們可以根據其中的思想剖析其他的比較複雜的解碼器, 或者根據其規則實現自己的自定義解碼器