netty原始碼閱讀之解碼之基於長度域解碼器分析
基於長度域解碼器LengthFieldBasedFrameDecoder我們主要分析以下三點:
1、計算需要抽取的資料包的長度
2、跳過位元組邏輯處理
3、丟棄模式下的處理
首先原始碼還是LengthFieldBasedFrameDecoder的decode方法:
/** * Create a frame out of the {@link ByteBuf} and return it. * * @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to * @param in the {@link ByteBuf} from which to read data * @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could * be created. */ protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { if (discardingTooLongFrame) { long bytesToDiscard = this.bytesToDiscard; int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes()); in.skipBytes(localBytesToDiscard); bytesToDiscard -= localBytesToDiscard; this.bytesToDiscard = bytesToDiscard; failIfNecessary(false); } if (in.readableBytes() < lengthFieldEndOffset) { return null; } int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset; long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder); if (frameLength < 0) { in.skipBytes(lengthFieldEndOffset); throw new CorruptedFrameException( "negative pre-adjustment length field: " + frameLength); } frameLength += lengthAdjustment + lengthFieldEndOffset; if (frameLength < lengthFieldEndOffset) { in.skipBytes(lengthFieldEndOffset); throw new CorruptedFrameException( "Adjusted frame length (" + frameLength + ") is less " + "than lengthFieldEndOffset: " + lengthFieldEndOffset); } if (frameLength > maxFrameLength) { long discard = frameLength - in.readableBytes(); tooLongFrameLength = frameLength; if (discard < 0) { // buffer contains more bytes then the frameLength so we can discard all now in.skipBytes((int) frameLength); } else { // Enter the discard mode and discard everything received so far. discardingTooLongFrame = true; bytesToDiscard = discard; in.skipBytes(in.readableBytes()); } failIfNecessary(true); return null; } // never overflows because it's less than maxFrameLength int frameLengthInt = (int) frameLength; if (in.readableBytes() < frameLengthInt) { return null; } if (initialBytesToStrip > frameLengthInt) { in.skipBytes(frameLengthInt); throw new CorruptedFrameException( "Adjusted frame length (" + frameLength + ") is less " + "than initialBytesToStrip: " + initialBytesToStrip); } in.skipBytes(initialBytesToStrip); // extract frame int readerIndex = in.readerIndex(); int actualFrameLength = frameLengthInt - initialBytesToStrip; ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength); in.readerIndex(readerIndex + actualFrameLength); return frame; }
一、計算需要抽取的資料包的長度
第一段程式碼是丟棄模式的,我們直接跳過看第二段:
if (in.readableBytes() < lengthFieldEndOffset) {
return null;
}
lengthFiledEndOffset上一篇文章我們分析過,在LengthFieldBasedFrameDecoder的構造器裡面初始化,
lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;
代表長度域偏移加上長度域長度。
那麼in.readableBytes()<lengthFieldEndOffset的話,說明資料包不完整,連lengthFiledOffset的長度都達不到,資料包肯定不完整,所以返回空,然後累加器就會繼續累加,知道資料包完整為止。
接下去看這一段:
int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
lengthFiledOffset是我們的相對偏移量,加上readerIndex就是在這個byteBuf裡面的絕對偏移量了,通過這個絕對偏移量,我們後續的操作就可以變得更加簡單。
接下去看:
long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
通過in這個byteBuf,actualLengthFieldOffset實際偏移量和lengthFieldLength就能找出我們資訊的長度。點選去看怎麼實現的:
/**
* Decodes the specified region of the buffer into an unadjusted frame length. The default implementation is
* capable of decoding the specified region into an unsigned 8/16/24/32/64 bit integer. Override this method to
* decode the length field encoded differently. Note that this method must not modify the state of the specified
* buffer (e.g. {@code readerIndex}, {@code writerIndex}, and the content of the buffer.)
*
* @throws DecoderException if failed to decode the specified region
*/
protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
buf = buf.order(order);
long frameLength;
switch (length) {
case 1:
frameLength = buf.getUnsignedByte(offset);
break;
case 2:
frameLength = buf.getUnsignedShort(offset);
break;
case 3:
frameLength = buf.getUnsignedMedium(offset);
break;
case 4:
frameLength = buf.getUnsignedInt(offset);
break;
case 8:
frameLength = buf.getLong(offset);
break;
default:
throw new DecoderException(
"unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
}
return frameLength;
}
簡單,就是在buf裡面找到長度域的位置,然後取出長度域裡面的值,作為frameLength返回。
然後:
if (frameLength < 0) {
in.skipBytes(lengthFieldEndOffset);
throw new CorruptedFrameException(
"negative pre-adjustment length field: " + frameLength);
}
如果取出的frameLength<0,那就是不符合條件的,丟擲異常。
然後:
frameLength += lengthAdjustment + lengthFieldEndOffset;
我們上一篇講過一個調整值,長度域裡面內容不一定表示的就是實際內容的長度,它可能加上長度域的長度或者head或者兩者都有,所以我們通過這個調整值來調整。
所以最終frameLength就是frameLength加上調整值,再加上lengthEndOffset。
我們以以下這個圖作為例子:
由於長度域表示的值是8,這個8包括了長度域2和長度域前面的兩個內容2,當然還有最終的資訊4,所以是8,所以需要調整 -4,也就是減去長度域2和長度域之前的內容2。最終上面那個表示式的值就是8,也就是最終讀取到的整段資訊的內容。
然後:
if (frameLength < lengthFieldEndOffset) {
in.skipBytes(lengthFieldEndOffset);
throw new CorruptedFrameException(
"Adjusted frame length (" + frameLength + ") is less " +
"than lengthFieldEndOffset: " + lengthFieldEndOffset);
}
這個frameLength是經過調整之後的frameLength,經過調整之後的frameLength是整段資訊內容也就是包括lengthFieldEndOffset的,但是現在小於lengthFieldEndOffset明顯不對,跳過lengthFieldEndOffset這段內容,並且丟擲異常。
接下去一段的內容是丟棄模式的處理,我們放最後。
二、跳過位元組邏輯處理
看這一段:
// never overflows because it's less than maxFrameLength
int frameLengthInt = (int) frameLength;
if (in.readableBytes() < frameLengthInt) {
return null;
}
到了這裡就說明我們的資料是正確的,沒有超過maxFrameLength。先轉換為10進位制。
如果當前可讀的內容小於我們需要讀取的內容,那麼就說明資料包不完整,繼續回去讀。
接下去:
if (initialBytesToStrip > frameLengthInt) {
in.skipBytes(frameLengthInt);
throw new CorruptedFrameException(
"Adjusted frame length (" + frameLength + ") is less " +
"than initialBytesToStrip: " + initialBytesToStrip);
}
如果需要跳過的直接大於當前已有的位元組,當然也是不對的,繼續丟擲異常。
最後才是真正跳過位元組邏輯的處理:
in.skipBytes(initialBytesToStrip);
取出readerIndex,並且求出真正的跳過之後資訊的長度:
// extract frame
int readerIndex = in.readerIndex();
int actualFrameLength = frameLengthInt - initialBytesToStrip;
最後把需要的資訊提取出來,並且移動指標,然後把取到的資訊返回:
ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
in.readerIndex(readerIndex + actualFrameLength);
return frame;
三、丟棄模式下的處理
我們先分析程式碼是如何進入丟棄模式的,也就是這一段:
if (frameLength > maxFrameLength) {
long discard = frameLength - in.readableBytes();
tooLongFrameLength = frameLength;
if (discard < 0) {
// buffer contains more bytes then the frameLength so we can discard all now
in.skipBytes((int) frameLength);
} else {
// Enter the discard mode and discard everything received so far.
discardingTooLongFrame = true;
bytesToDiscard = discard;
in.skipBytes(in.readableBytes());
}
failIfNecessary(true);
return null;
}
當解析到的frameLength大於maxFrameLength了,那就是可能要進入丟棄模式了。
discard=frameLength-in.readableBytes
frameLength本來這一段都是要丟棄的,但是有可能當前可讀的資料還小於frameLength的長度,那就還有discard這麼多的位元組,後續需要丟棄。
如果discard<0,說明當前可讀的大於frameLength長度了,frameLength這麼長的資料就直接丟棄:
if (discard < 0) {
// buffer contains more bytes then the frameLength so we can discard all now
in.skipBytes((int) frameLength);
}
否則,就要進入丟棄模式:
進入丟棄模式,並且需要記錄剩下的還沒有丟棄的資料,然後跳過當前可讀的資料。
if (discard < 0) {
...
} else {
// Enter the discard mode and discard everything received so far.
discardingTooLongFrame = true;
bytesToDiscard = discard;
in.skipBytes(in.readableBytes());
}
最後,呼叫failIfNecessary,因為是第一次,所以我們firstDetectionOfTooLongFrame為true。
我們看failIfNecessary:
private void failIfNecessary(boolean firstDetectionOfTooLongFrame) {
if (bytesToDiscard == 0) {
// Reset to the initial state and tell the handlers that
// the frame was too large.
long tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0;
discardingTooLongFrame = false;
if (!failFast ||
failFast && firstDetectionOfTooLongFrame) {
fail(tooLongFrameLength);
}
} else {
// Keep discarding and notify handlers if necessary.
if (failFast && firstDetectionOfTooLongFrame) {
fail(tooLongFrameLength);
}
}
}
如果還需要丟棄的資料為0,那就可以結束丟棄模式了,並且tooLongFrameLength的值賦給別的變數,方便做後續異常處理的引數。如果是非快速失敗模式,或者是快速失敗模式,並且是第一次進來(因為我們上一步判斷discard的時候只用了>0,沒有考慮到=0的情況,所以也有可能進入到這個判斷,這種情況是第一次進入丟棄模式,但是馬上在這裡又還原了,所以也可以呼叫fail方法),那就呼叫fail丟擲異常。
否則,符合failFast模式並且是第一次進來也是丟擲異常。
然後我們看函式一開始進來的程式碼:
if (discardingTooLongFrame) {
long bytesToDiscard = this.bytesToDiscard;
int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
in.skipBytes(localBytesToDiscard);
bytesToDiscard -= localBytesToDiscard;
this.bytesToDiscard = bytesToDiscard;
failIfNecessary(false);
}
這裡就是在丟棄模式下需要處理的事情了。
比較需要丟棄的資料的長度和可讀的長度,取最小值,就是是我們當前可以丟棄的長度:
int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
然後跳過當前可以跳過的長度:
in.skipBytes(localBytesToDiscard);
最後把之前需要丟棄的長度,減去當前丟棄了的長度,就得到還需要丟棄的長度,這個還需要丟棄的長度>=0,大於0說明可讀長度還小於bytesToDiscard,還有內容需要丟棄,等於0說明都丟棄完了;然後記錄還需要丟棄的長度。
bytesToDiscard -= localBytesToDiscard;
this.bytesToDiscard = bytesToDiscard;
最後還是呼叫failIfNecessary,但是這次不再是第一次了,所以firstDetectionOfTooLongFrame傳入false。和之前第一次的failIfNecessary比較一下,會有更加深入的理解。