1. 程式人生 > >netty原始碼閱讀之解碼之基於長度域解碼器分析

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比較一下,會有更加深入的理解。