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, 代表本次沒有解析出任何資料
以上就是行解碼器的相關邏輯