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

Netty原始碼分析第6章(解碼器)---->第3節: 行解碼器

 

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

 

第三節: 行解碼器

 

這一小節瞭解下行解碼器LineBasedFrameDecoder, 行解碼器的功能是一個位元組流, 以\r\n或者直接以\n結尾進行解碼, 也就是以換行符為分隔進行解析

同樣, 這個解碼器也繼承了ByteToMessageDecoder

首先看其引數:

//資料包的最大長度, 超過該長度會進行丟棄模式
private final int maxLength;
//超出最大長度是否要丟擲異常
private final boolean failFast;
//最終解析的資料包是否帶有換行符
private final boolean stripDelimiter; //為true說明當前解碼過程為丟棄模式 private boolean discarding; //丟棄了多少位元組 private int discardedBytes;

其中的丟棄模式, 我們會在原始碼中看到其中的含義

我們看其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方法和我們上一小節分析的decode方法一樣, 呼叫過載的decode方法, 並將解碼後的內容放到out集合中

我們跟到過載的decode方法中:

protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
    //找這行的結尾
    final int eol = findEndOfLine(buffer);
    if (!discarding) {
        
if (eol >= 0) { final ByteBuf frame; //計算從換行符到可讀位元組之間的長度 final int length = eol - buffer.readerIndex(); //拿到分隔符長度, 如果是\r\n結尾, 分隔符長度為2 final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1; //如果長度大於最大長度 if (length > maxLength) { //指向換行符之後的可讀位元組(這段資料完全丟棄) buffer.readerIndex(eol + delimLength); //傳播異常事件 fail(ctx, length); return null; } //如果這次解析的資料是有效的 //分隔符是否算在完整資料包裡 //true為丟棄分隔符 if (stripDelimiter) { //擷取有效長度 frame = buffer.readRetainedSlice(length); //跳過分隔符的位元組 buffer.skipBytes(delimLength); } else { //包含分隔符 frame = buffer.readRetainedSlice(length + delimLength); } return frame; } else { //如果沒找到分隔符(非丟棄模式) //可讀位元組長度 final int length = buffer.readableBytes(); //如果朝超過能解析的最大長度 if (length > maxLength) { //將當前長度標記為可丟棄的 discardedBytes = length; //直接將讀指標移動到寫指標 buffer.readerIndex(buffer.writerIndex()); //標記為丟棄模式 discarding = true; //超過最大長度丟擲異常 if (failFast) { fail(ctx, "over " + discardedBytes); } } //沒有超過, 則直接返回 return null; } } else { //丟棄模式 if (eol >= 0) { //找到分隔符 //當前丟棄的位元組(前面已經丟棄的+現在丟棄的位置-寫指標) final int length = discardedBytes + eol - buffer.readerIndex(); //當前換行符長度為多少 final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1; //讀指標直接移到換行符+換行符的長度 buffer.readerIndex(eol + delimLength); //當前丟棄的位元組為0 discardedBytes = 0; //設定為未丟棄模式 discarding = false; //丟棄完位元組之後觸發異常 if (!failFast) { fail(ctx, length); } } else { //累計已丟棄的位元組個數+當前可讀的長度 discardedBytes += buffer.readableBytes(); //移動 buffer.readerIndex(buffer.writerIndex()); } return null; } }

 final int eol = findEndOfLine(buffer) 這裡是找當前行的結尾的索引值, 也就是\r\n或者是\n:

 

 

6-3-1

圖中不難看出, 如果是以\n結尾的, 返回的索引值是\n的索引值, 如果是\r\n結尾的, 返回的索引值是\r的索引值

我們看findEndOfLine(buffer)方法:

private static int findEndOfLine(final ByteBuf buffer) {
    //找到/n這個位元組
    int i = buffer.forEachByte(ByteProcessor.FIND_LF);
    //如果找到了, 並且前面的字元是-r, 則指向/r位元組
    if (i > 0 && buffer.getByte(i - 1) == '\r') {
        i--;
    }
    return i;
}

這裡通過一個forEachByte方法找\n這個位元組, 如果找到了, 並且前面是\r, 則返回\r的索引, 否則返回\n的索引

回到過載的decode方法中:

 if (!discarding) 判斷是否為非丟棄模式, 預設是就是非丟棄模式, 所以進入if中

 if (eol >= 0) 如果找到了換行符, 我們看非丟棄模式下找到換行符的相關邏輯:

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;

首先獲得換行符到可讀位元組之間的長度, 然後拿到換行符的長度, 如果是\n結尾, 那麼長度為1, 如果是\r結尾, 長度為2

 if (length > maxLength) 帶表如果長度超過最大長度, 則直接通過 readerIndex(eol + delimLength) 這種方式, 將讀指標指向換行符之後的位元組, 說明換行符之前的位元組需要完全丟棄

6-3-2

丟棄之後通過fail方法傳播異常, 並返回null

繼續往下看, 走到下一步, 說明解析出來的資料長度沒有超過最大長度, 說明是有效資料包

 if (stripDelimiter) 表示是否要將分隔符放在完整資料包裡面, 如果是true, 則說明要丟棄分隔符, 然後擷取有效長度, 並跳過分隔符長度

將包含分隔符進行擷取

以上就是非丟棄模式下找到換行符的相關邏輯

我們再看非丟棄模式下沒有找到換行符的相關邏輯, 也就是非丟棄模式下,  if (eol >= 0) 中的else塊:

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

首先通過 final int length = buffer.readableBytes() 獲取所有的可讀位元組數

然後判斷可讀位元組數是否超過了最大值, 如果超過最大值, 則屬性discardedBytes標記為這個長度, 代表這段內容要進行丟棄

6-3-3

 buffer.readerIndex(buffer.writerIndex()) 這裡直接將讀指標移動到寫指標, 並且將discarding設定為true, 就是丟棄模式

如果可讀位元組沒有超過最大長度, 則返回null, 表示什麼都沒解析出來, 等著下次解析

我們再看丟棄模式的處理邏輯, 也就是 if (!discarding) 中的else塊:

首先這裡也分兩種情況, 根據 if (eol >= 0) 判斷是否找到了分隔符, 我們首先看找到分隔符的解碼邏輯:

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);
}

如果找到換行符, 則需要將換行符之前的資料全部丟棄掉

6-3-4

 final int length = discardedBytes + eol - buffer.readerIndex() 這裡獲得丟棄的位元組總數, 也就是之前丟棄的位元組數+現在需要丟棄的位元組數

然後計算換行符的長度, 如果是\n則是1, \r\n就是2

 buffer.readerIndex(eol + delimLength) 這裡將讀指標移動到換行符之後的位置

然後將discarding設定為false, 表示當前是非丟棄狀態

我們再看丟棄模式未找到換行符的情況, 也就是丟棄模式下,  if (eol >= 0) 中的else塊:

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

這裡做的事情非常簡單, 就是累計丟棄的位元組數, 並將讀指標移動到寫指標, 也就是將資料全部丟棄

 

最後在丟棄模式下, decode方法返回null, 代表本次沒有解析出任何資料

以上就是行解碼器的相關邏輯