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類的解碼器, 這個幾個解碼器邏輯都比較簡單, 同學們可以根據其中的思想剖析其他的比較複雜的解碼器, 或者根據其規則實現自己的自定義解碼器