Netty源碼愫讀(四)ChannelHandler相關源碼學習

1、ChannelHandler源碼解析

1.1、ChannelHandler功能說明

ChannelHandler類似servlet的Filter過濾器,其負責對I/O事件或I/O操作進行攔截和處理,可以選擇性地攔截和處理自己感興趣的事件,也可以透傳和終止事件傳播。其實現原理是基于pipeline構成事件處理責任鏈,inbound或outbound事件沿著處理責任鏈中的ChannelHandler傳播處理。

基于ChannelHandler接口,可以方便實現業務邏輯的個性定制,如打印日志、統一異常封裝、性能統計等。

ChannelHandler支持如下注解:

  • @Sharable:表示多個ChannelPipeline可以共享同一個ChannelHandler,否則不允許一個ChannelHandler添加到多個Pipeline中。
  • @Skip:被Skip注解的方法不會被調用,直接被忽略。

1.2、Channel的生命周期

Channel有以下狀態模型:

  • channelUnregistered:Channel已創建,但還未注冊的EventLoop中。
  • channelRegistered:Channel已經被注冊到EventLoop中;
  • channelActive:Channel處于活動狀態,即其已經連接到遠程節點,可以進行數據收發處理;
  • channelInactive:Channel還未連接帶遠程節點;

Channel各狀態轉換圖如下:

Channel各狀態轉換圖.png

你會看到多個channelRegistered和channelUnregistered狀態的變化,而永遠只有一個channelActive和channelInactive的狀態,因為一個通道在其生命周期內只能連接一次,之后就會被回收;重新連接,則是創建一個新的通道。

1.3、ChannelHandler類繼承圖

ChannelHandler類繼承圖.png

1.4、ChannelHandler接口

此接口定義了ChannelHandler在pipeline中的生命周期操作。在ChannelHandler被添加到ChannelPipeline中或從ChannelPipeline中移除是會調用這些操作。

操作方法如下:

  • handlerAdded:當把ChannelHandler 添加到ChannelPipeline 中時被調用,只有調用此方法后,I/O事件的傳播才會經由此ChannelHandler處理,否則直接跳過此ChannelHandler;
  • handlerRemoved:當從ChannelPipeline 中移除ChannelHandler 時被調用,當被移除后,Channel事件傳播將不會經由此ChannelHandler;
  • exceptionCaught:當處理過程中在ChannelPipeline 中有錯誤產生時被調用;

1.5、ChannelInboundHandler接口

此接口定義了Channel的I/O事件接口,這些接口與Channel的生命周期密切相關。

方法名稱 功能說明
channelRegistered 當Channel 已經注冊到它的EventLoop 并且能夠處理I/O 時被調用
channelUnregistered 當Channel 從它的EventLoop 注銷并且無法處理任何I/O 時被調用
channelActive 當Channel 處于活動狀態時被調用;Channel 已經連接/綁定并且已經就緒
channelInactive 當Channel 離開活動狀態并且不再連接它的遠程節點時被調用
channelReadComplete 當Channel上的一個讀操作完成時被調用
channelRead 當從Channel 讀取數據時被調用
ChannelWritabilityChanged 當Channel 的可寫狀態發生改變時被調用。用戶可以確保寫操作不會完成得太快(以避免發生OutOfMemoryError)或者可以在Channel 變為再次可寫時恢復寫入。可以通過調用Channel 的isWritable()方法來檢測Channel 的可寫性。與可寫性相關的閾值可以通過Channel.config().setWriteHighWaterMark()和Channel.config().setWriteLowWaterMark()方法來設置
userEventTriggered 當ChannelnboundHandler.fireUserEventTriggered()方法被調用時被調用,因為一個POJO 被傳經了ChannelPipeline

對于channelRead()方法,當某個子類重寫此方法時,其負責顯示地釋放與池化ByteBuf實例相關的內存。Netty為此提供了ReferenceCountUtil.release()工具方法進行內存釋放。同時,Netty也提供了SimpleChannelInboundHandler,其會自動釋放ButBuf內存。

實現源碼如下:

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    boolean release = true;
    try {
        if (acceptInboundMessage(msg)) {
            @SuppressWarnings("unchecked")
            I imsg = (I) msg;
            channelRead0(ctx, imsg);
        } else {
            release = false;
            ctx.fireChannelRead(msg);
        }
    } finally {
        if (autoRelease && release) {
            ReferenceCountUtil.release(msg);
        }
    }
}

1.6、ChannelOutboundHandler接口

出站操作和數據將由ChannelOutboundHandler接口處理。其方法將被Channel、ChannelPipeline及ChannelHandlerContext調用。

方法名稱 功能說明
bind(ChannelHandlerContext,SocketAddress,ChannelPromise) 當請求將Channel 綁定到本地地址時被調用
connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise) 當請求將Channel 連接到遠程節點時被調用
disconnect(ChannelHandlerContext,ChannelPromise) 當請求將Channel 從遠程節點斷開時被調用
close(ChannelHandlerContext,ChannelPromise) 當請求關閉Channel 時被調用
deregister(ChannelHandlerContext,ChannelPromise) 當請求將Channel 從它的EventLoop 注銷時被調用
read(ChannelHandlerContext) 當請求從Channel 讀取更多的數據時被調
flush(ChannelHandlerContext) 當請求通過Channel 將入隊數據沖刷到遠程節點時被調用
write(ChannelHandlerContext,Object,ChannelPromise) 當請求通過Channel 將數據寫到遠程節點時被調用

ChannelPromise與ChannelFuture:ChannelOutboundHandler中的大部分方法都需要一個ChannelPromise參數,以便在操作完成時得到通知。ChannelPromise是ChannelFuture的一個子類,其定義了一些可寫的方法,如setSuccess()和setFailure(),從而使ChannelFuture不可變。

1.7、ChannelHandlerAdapter抽象類

ChannelHandlerAdapter是ChannelHandler的抽象實現類,其主要實現了isSharable()方法,此方法判斷ChannelHandler是否為多ChannelPipeline共享的。

1.8、ChannelInboundHandlerAdapter類

ChannelInboundHandlerAdapter為ChannelInboundHandler的實現類,其實現非常簡單,所有的方法都是調用其對應的ChannelHandlerContext對應的fileXXX進行處理。

源碼示例如下:

public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
    ctx.fireChannelRegistered();
}

1.9、ChannelOutboundHandlerAdapter類

ChannelOutboundHandlerAdapter為ChannelOutboundHandler的實現類,其實現非常簡單,直接調用對應的ChannelHandlerContext對應方法。

源碼示例如下:

public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
        ChannelPromise promise) throws Exception {
    ctx.bind(localAddress, promise);
}

2、Decoder(解碼器)

2.1、粘包、拆包

粘包、拆包是Socket編程中最常見的問題,TCP是個流協議,TCP底層并不了解上層業務的具體含義,其會根據TCP緩沖區的實際情況進行包的劃分。

在所以業務上:

  • 一個完整的包可能會被TCP拆分為多個包進行發送(拆包);
  • 多個小的包也有可能被封裝成一個大的包進行發送(粘包);

2.1.1、粘包、拆包產生原因

產生原因大致有以下三種:

  • 應用程序寫入的字節大小大于Socket發送緩沖區大小
  • 進行MSS大小的TCP,MSS是最大報文段長度的縮寫,是TCP報文段中的數據字段最大長度,MSS=TCP報文段長度-TCP首部長度
  • 以太網的Payload大于MTU,進行IP分片,MTU是最大傳輸單元的縮寫,以太網的MTU為1500字節

2.1.1、粘包、拆包解決思路

由于底層的TCP無法理解上層的業務數據,所以在底層是無法保證數據包不被拆分和重組的,這個問題只能通過上層的應用協議棧設計來解決,根據業界的主流協議的解決方案,可以歸納如下:

  • 消息定長,例如每個報文的大小固定為200字節,如果不夠空位補空格
  • 包尾增加回車換行符進行分割,例如FTP協議
  • 將消息分為消息頭和消息體,消息頭中包含表示長度的字段,通常涉及思路為消息頭的第一個字段使用int32來表示消息的總長度
  • 更復雜的應用層協議

拆包思路:

當數據滿足了 解碼條件時,將其拆開。放到數組。然后發送到業務 handler 處理。

半包思路:

當讀取的數據不夠時,先存起來,直到滿足解碼條件后,放進數組。送到業務 handler 處理。

2.2、ByteToMessageDecoder

ByteToMessageDecoder解碼器主要功能為將ByteBuf緩沖區中的數據轉換為對象,用戶的解碼器基礎ByteToMessageDecoder,只需實現decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)抽象方法即可完成將ByteBuf到POJO對象的解碼。

由于ByteToMessageDecoder并未考慮TCP粘包和組包等場景,讀半包需要用戶解碼器自己負責處理。因此,大多數場景不會直接繼承ByteToMessageDecoder進行數據解碼處理,而是繼承一些更高級的解碼器來屏蔽半包的處理。

2.2.1、成員變量

ByteToMessageDecoder成員變量如下:

private static final byte STATE_INIT = 0;
private static final byte STATE_CALLING_CHILD_DECODE = 1;
private static final byte STATE_HANDLER_REMOVED_PENDING = 2;

ByteBuf cumulation;
private Cumulator cumulator = MERGE_CUMULATOR;
private boolean singleDecode;
private boolean decodeWasNull;
private boolean first;
/**
 * A bitmask where the bits are defined as
 * <ul>
 *     <li>{@link #STATE_INIT}</li>
 *     <li>{@link #STATE_CALLING_CHILD_DECODE}</li>
 *     <li>{@link #STATE_HANDLER_REMOVED_PENDING}</li>
 * </ul>
 */
private byte decodeState = STATE_INIT;
private int discardAfterReads = 16;
private int numReads;

成員變量說明:

  • cumulation:累計器緩存

  • cumulator:累計器,對從ByteBuf讀取的數據進行累積處理;

  • singleDecode:是否為單消息解碼器;

  • decodeWasNull:解碼的數據為空;

  • first:第一次讀取數據;

  • decodeState :解碼器狀態;

  • discardAfterReads :讀取多少次后對累計器緩沖區進行discardSomeReadBytes()處理。

  • numReads:記錄讀取次數;

2.2.2、累計器解讀

成員變量cumulator為ByteToMessageDecoder的累計器,其有兩種實現:

MERGE_CUMULATOR:合并累計器

public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
    @Override
    public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
        final ByteBuf buffer;
        if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
                || cumulation.refCnt() > 1 || cumulation.isReadOnly()) {
            // Expand cumulation (by replace it) when either there is not more room in the buffer
            // or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
            // duplicate().retain() or if its read-only.
            //
            // See:
            // - https://github.com/netty/netty/issues/2327
            // - https://github.com/netty/netty/issues/1764
            buffer = expandCumulation(alloc, cumulation, in.readableBytes());
        } else {
            buffer = cumulation;
        }
        buffer.writeBytes(in);
        in.release();
        return buffer;
    }
};

從cumulate方法可知,其主要是將 unsafe.read 傳遞過來的 ByteBuf 的內容寫入到 cumulation 累積區中,然后釋放掉舊的內容,由于這個變量是成員變量,因此可以多次調用 channelRead 方法寫入。同時這個方法也考慮到了擴容的問題,當容量 不夠時會先進行擴容處理。

COMPOSITE_CUMULATOR:混合累計器

/**
 * Cumulate {@link ByteBuf}s by add them to a {@link CompositeByteBuf} and so do no memory copy whenever possible.
 * Be aware that {@link CompositeByteBuf} use a more complex indexing implementation so depending on your use-case
 * and the decoder implementation this may be slower then just use the {@link #MERGE_CUMULATOR}.
 */
public static final Cumulator COMPOSITE_CUMULATOR = new Cumulator() {
    @Override
    public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
        ByteBuf buffer;
        if (cumulation.refCnt() > 1) {
            // Expand cumulation (by replace it) when the refCnt is greater then 1 which may happen when the user
            // use slice().retain() or duplicate().retain().
            //
            // See:
            // - https://github.com/netty/netty/issues/2327
            // - https://github.com/netty/netty/issues/1764
            buffer = expandCumulation(alloc, cumulation, in.readableBytes());
            buffer.writeBytes(in);
            in.release();
        } else {
            CompositeByteBuf composite;
            if (cumulation instanceof CompositeByteBuf) {
                composite = (CompositeByteBuf) cumulation;
            } else {
                composite = alloc.compositeBuffer(Integer.MAX_VALUE);
                composite.addComponent(true, cumulation);
            }
            composite.addComponent(true, in);
            buffer = composite;
        }
        return buffer;
    }
};

由以上源碼可知,此累計器利用CompositeByteBuf類型的ByteBuf,將讀取到的數據直接作為一個Component放到CompositeByteBuf中,相較于拷貝,此種累計器性能會更好,但同時也更復雜。

2.2.3、讀取數據處理

channelRead()實現源碼:

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    if (msg instanceof ByteBuf) {
        CodecOutputList out = CodecOutputList.newInstance();
        try {
            ByteBuf data = (ByteBuf) msg;
            first = cumulation == null;
            if (first) {
                cumulation = data;
            } else {
                cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
            }
            callDecode(ctx, cumulation, out);
        } catch (DecoderException e) {
            throw e;
        } catch (Exception e) {
            throw new DecoderException(e);
        } finally {
            if (cumulation != null && !cumulation.isReadable()) {
                numReads = 0;
                cumulation.release();
                cumulation = null;
            } else if (++ numReads >= callDecode) {
                // We did enough reads already try to discard some bytes so we not risk to see a OOME.
                // See https://github.com/netty/netty/issues/4275
                numReads = 0;
                discardSomeReadBytes();
            }

            int size = out.size();
            decodeWasNull = !out.insertSinceRecycled();
            fireChannelRead(ctx, out, size);
            out.recycle();
        }
    } else {
        ctx.fireChannelRead(msg);
    }
}

static void fireChannelRead(ChannelHandlerContext ctx, CodecOutputList msgs, int numElements) {
    for (int i = 0; i < numElements; i ++) {
        ctx.fireChannelRead(msgs.getUnsafe(i));
    }
}

主要處理流程:

  • 從對象池中取出一個空的數組。
  • 判斷成員變量是否是第一次使用,(注意,既然使用了成員變量,所以這個 handler 不能是 handler 的。)將 unsafe 中傳遞來的數據寫入到這個 cumulation 累積區中。
  • 寫到累積區后,調用子類的 callDecode方法,嘗試將累積區的內容解碼。
  • 解碼完成后,若累計器無可讀的數據,則清除讀計數和累計器緩沖區,如果讀次數大于callDecode(默認為16次),則調用discardSomeReadBytes對累計器的緩沖區進行壓縮處理。
  • 如果解碼的對象為空,則設置decodeWasNull 為true;
  • 調用fireChannelRead()將解碼的對象list遍歷交由fireChannelRead()進行處理。
  • 將數組清空。并還給對象池。

2.2.3、解碼處理

解碼實現源碼:

protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    try {
        while (in.isReadable()) {
            int outSize = out.size();

            if (outSize > 0) {
                fireChannelRead(ctx, out, outSize);
                out.clear();

                // Check if this handler was removed before continuing with decoding.
                // If it was removed, it is not safe to continue to operate on the buffer.
                //
                // See:
                // - https://github.com/netty/netty/issues/4635
                if (ctx.isRemoved()) {
                    break;
                }
                outSize = 0;
            }

            int oldInputLength = in.readableBytes();
            decodeRemovalReentryProtection(ctx, in, out);

            // Check if this handler was removed before continuing the loop.
            // If it was removed, it is not safe to continue to operate on the buffer.
            //
            // See https://github.com/netty/netty/issues/1664
            if (ctx.isRemoved()) {
                break;
            }

            if (outSize == out.size()) {
                if (oldInputLength == in.readableBytes()) {
                    break;
                } else {
                    continue;
                }
            }

            if (oldInputLength == in.readableBytes()) {
                throw new DecoderException(
                        StringUtil.simpleClassName(getClass()) +
                                ".decode() did not read anything but decoded a message.");
            }

            if (isSingleDecode()) {
                break;
            }
        }
    } catch (DecoderException e) {
        throw e;
    } catch (Exception cause) {
        throw new DecoderException(cause);
    }
}


final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
        throws Exception {
    decodeState = STATE_CALLING_CHILD_DECODE;
    try {
        decode(ctx, in, out);
    } finally {
        boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
        decodeState = STATE_INIT;
        if (removePending) {
            handlerRemoved(ctx);
        }
    }
}

主要處理流程:

  • 循環讀取緩沖區數據,如果有上次解碼成功而未傳播給下個ChannelHandler進行處理的對象,則交由下個Handler進行處理,并清除緩存的對象列表;
  • 讀取數據并調用 decodeRemovalReentryProtection 方法,內部調用了子類重寫的 decode 方法,很明顯,這里是個模板模式。decode 方法的邏輯就是將累積區的內容按照約定進行解碼,如果成功解碼,就添加到數組中。同時該方法也會檢查該 handler 的狀態,如果被移除出 pipeline 了,就將累積區的內容直接刷新到后面的 handler 中。
  • 如果 Context 節點被移除了,直接結束循環。如果解碼前的數組大小和解碼后的數組大小相等,且累積區的可讀字節數沒有變化,說明此次讀取什么都沒做,就直接結束。如果字節數變化了,說明雖然數組沒有增加,但確實在讀取字節,就再繼續讀取。
  • 如果上面的判斷過了,說明數組讀到數據了,但如果累積區的 readIndex 沒有變化,則拋出異常,說明沒有讀取數據,但數組卻增加了,子類的操作是不對的。
  • 如果是個單次解碼器,解碼一次就直接結束了。

2.3、MessageToMessageDecoder

MessageToMessageDecoder負責將一個POJO對象解碼為另一個POJO對象。

2.3.1、數據讀取解析

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    CodecOutputList out = CodecOutputList.newInstance();
    try {
        if (acceptInboundMessage(msg)) {
            @SuppressWarnings("unchecked")
            I cast = (I) msg;
            try {
                decode(ctx, cast, out);
            } finally {
                ReferenceCountUtil.release(cast);
            }
        } else {
            out.add(msg);
        }
    } catch (DecoderException e) {
        throw e;
    } catch (Exception e) {
        throw new DecoderException(e);
    } finally {
        int size = out.size();
        for (int i = 0; i < size; i ++) {
            ctx.fireChannelRead(out.getUnsafe(i));
        }
        out.recycle();
    }
}

主要處理流程:

  • 通過RecyclableArrayList創建新的可循環利用的list對象;
  • 對解碼的消息類型進行判斷,通過則調用decode()方法進行解碼處理,處理完成釋放緩存對象;
  • 類型匹配失敗,則直接將對象放到out的list中;
  • 最終遍歷解碼后的POJOlist,將各個解碼對象發給下個Handler處理;
  • 對象池釋放;

2.4、LineBasedFrameDecoder

LineBasedFrameDecoder是Netty提供的一種解碼器,專門用于以換行符為分割的消息的解碼,能夠處理\n和\r\n的換行符,其實現了對粘包、半包等的處理。LineBasedFrameDecoder繼承ByteToMessageDecoder,這樣對于轉換字節為POJO對象的底層工作就交給ByteToMessageDecoder類來實現了,LineBasedFrameDecoder類只需要負責對消息的字節流進行解包即可。

處理大體思路:

  • 沒有需要忽略的數據:

(1)、若找到換行符,若長度超過最大長度,則直接失敗處理;否則讀取數據,并返回數據;

(2)、若未找到換行符,若長度超過最大長度,則進行需要忽略的數據的相關參數設置,并等待底層繼續讀取數據;

  • 有需要忽略的數據:

(1)、若找到換行符,則直接忽略掉上次的半包數據及本次讀取的數據,并設置無忽略相關的參數;

(2)、若未找到換行符。則直接設置忽略相關的參數,等待底層繼續讀取數據;

解碼實現源碼:

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();
            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;
        } else {
            final int length = buffer.readableBytes();
            if (length > maxLength) {
                discardedBytes = length;
                buffer.readerIndex(buffer.writerIndex());
                discarding = true;
                offset = 0;
                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);
            discardedBytes = 0;
            discarding = false;
            if (!failFast) {
                fail(ctx, length);
            }
        } else {
            discardedBytes += buffer.readableBytes();
            buffer.readerIndex(buffer.writerIndex());
        }
        return null;
    }
}

private int findEndOfLine(final ByteBuf buffer) {
    int totalLength = buffer.readableBytes();
    int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF);
    if (i >= 0) {
        offset = 0;
        if (i > 0 && buffer.getByte(i - 1) == '\r') {
            i--;
        }
    } else {
        offset = totalLength;
    }
    return i;
}

主要處理流程:

  • 查找行尾,findEndOfLine()中主要查找字符'\n',找到并檢查其其前一個字符是否為’\r‘;
  • 如果不能忽略數據且找到換行符,則一行數據進行處理;如果數據長度大于maxLength,則設置讀索引readerIndex并進行異常處理;如果返回的數據需要忽略換行符,則只讀取數據并跳過換行符,否則讀取數據及換行符;
  • 如果不能忽略但未找到換行符,若可讀數據長度大于maxLength,則設置discardedBytes為緩沖區可讀數據的長度,設置讀索引readerIndex為寫索引writterIndex,設置discarding為true表示有忽略的數據;偏移量設置為0;若設置了快速失敗則直接報異常;若可讀數據長度小于等于maxLength則不對數據進行處理;
  • 當有忽略的數據時,若找到換行符,則設置讀索引readerIndex為換行符之后,設置discardedBytes為0,設置discarding為false,并做快速失敗處理;
  • 當有忽略的數據時,若未找到換行符,則這是discardedBytes為可讀字節數并設置讀索引readerIndex為寫索引writterIndex;

2.5、DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder實現了根據一個或多個分隔符對ByteBuf進行解碼處理;其處理流程大體和LineBasedFrameDecoder類似。

處理大體思路:

  • 根據分隔符列表遍歷緩沖區數據,找到匹配某個分隔符的最短的索引位置及其對應的分隔符;
  • 分隔符找到,并且有需要忽略的數據,則對數據做忽略及異常處理;否則處理數據并返回數據;
  • 分隔符未找到,若無需要忽略的數據,若可讀數據長度大于最大長度,則進行忽略數據的相關處理;
  • 分隔符為找到,若喲需要忽略的數據,則直接進行忽略數據的處理。
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
    if (lineBasedDecoder != null) {
        return lineBasedDecoder.decode(ctx, buffer);
    }
    // Try all delimiters and choose the delimiter which yields the shortest frame.
    int minFrameLength = Integer.MAX_VALUE;
    ByteBuf minDelim = null;
    for (ByteBuf delim: delimiters) {
        int frameLength = indexOf(buffer, delim);
        if (frameLength >= 0 && frameLength < minFrameLength) {
            minFrameLength = frameLength;
            minDelim = delim;
        }
    }

    if (minDelim != null) {
        int minDelimLength = minDelim.capacity();
        ByteBuf frame;

        if (discardingTooLongFrame) {
            // We've just finished discarding a very large frame.
            // Go back to the initial state.
            discardingTooLongFrame = false;
            buffer.skipBytes(minFrameLength + minDelimLength);

            int tooLongFrameLength = this.tooLongFrameLength;
            this.tooLongFrameLength = 0;
            if (!failFast) {
                fail(tooLongFrameLength);
            }
            return null;
        }

        if (minFrameLength > maxFrameLength) {
            // Discard read frame.
            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) {
                // Discard the content of the buffer until a delimiter is found.
                tooLongFrameLength = buffer.readableBytes();
                buffer.skipBytes(buffer.readableBytes());
                discardingTooLongFrame = true;
                if (failFast) {
                    fail(tooLongFrameLength);
                }
            }
        } else {
            // Still discarding the buffer since a delimiter is not found.
            tooLongFrameLength += buffer.readableBytes();
            buffer.skipBytes(buffer.readableBytes());
        }
        return null;
    }
}

處理流程:

  • 若分隔符為換行分隔符,則直接調用換行分隔符的解碼器進行處理;
  • 遍歷所有分隔符,查找到索引最小的分隔符及其對應的索引;
  • 若分隔符找到,若上次有需要忽略的數據,則直接進行數據跳過處理;否則讀取數據并返回數;
  • 若未找到分隔符,若無需要忽略的數據,則判斷數據長度是否大于最大長度,是則進行忽略數據處理,否則不進行處理;

2.6、LengthFieldBasedFrameDecoder

LengthFieldBasedFrameDecoder是基于自定義數據長度進行解碼的解碼器;

2.6.1、基本屬性

private final ByteOrder byteOrder;
private final int maxFrameLength;
private final int lengthFieldOffset;
private final int lengthFieldLength;
private final int lengthFieldEndOffset;
private final int lengthAdjustment;
private final int initialBytesToStrip;
private final boolean failFast;
private boolean discardingTooLongFrame;
private long tooLongFrameLength;
private long bytesToDiscard;

byteOrder:字節序,大端或小端;
maxFrameLength:最大數據長度,超過此長度說明數據包錯誤;
lengthFieldOffset:length字段相對于讀索引readerIndex的偏移量;
lengthFieldLength:length字段的字節長度,其長度只能為1、2、3、4、8中的一個,其他長度錯誤;
lengthFieldEndOffset:表示緊跟長度域字段后面的第一個字節的在整個數據包中的偏移量
lengthAdjustment:計算調整后的長度字段的偏移量;
initialBytesToStrip:初始需要跳過的長度;
failFast:快速失敗標志;
discardingTooLongFrame:忽略過長數據包標志;
tooLongFrameLength:過長數據的當前長度;
bytesToDiscard:需要忽略的字節數;

2.6.1、解碼實現流程

decode()實現源碼:

protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
    if (discardingTooLongFrame) {
        discardingTooLongFrame(in);
    }

    if (in.readableBytes() < lengthFieldEndOffset) {
        return null;
    }

    int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
    long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);

    if (frameLength < 0) {
        failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
    }

    frameLength += lengthAdjustment + lengthFieldEndOffset;

    if (frameLength < lengthFieldEndOffset) {
        failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset);
    }

    if (frameLength > maxFrameLength) {
        exceededFrameLength(in, frameLength);
        return null;
    }

    // never overflows because it's less than maxFrameLength
    int frameLengthInt = (int) frameLength;
    if (in.readableBytes() < frameLengthInt) {
        return null;
    }

    if (initialBytesToStrip > frameLengthInt) {
        failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, 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;
}

private void exceededFrameLength(ByteBuf in, long frameLength) {
    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);
}

處理流程:

  • 上一次數據包過長,并且有未讀取完的需要跳過的數據,則進行跳過處理;
  • 如果可讀數據長度小于長度字段的下個字節偏移量,表示數據長度不夠,不做處理;
  • 獲取長度字段并對長度字段進行處理,當長度小于0或小于長度字段下個字節偏移量,則進行失敗處理;
  • 當包長度大于最大長度時,調用exceededFrameLength()進行數據跳過處理;
  • 當可讀長度小于包中標識的長度,表示數據為讀完,不做處理;
  • 當包長度小于需要跳過的長度,進行失敗處理;
  • 讀取緩沖區中的數據;

2.7、其他解碼器

Netty內置了豐富的ChannelHandler,例如處理Http協議的Handler、處理Websocket協議的Handler、處理SSL安全套接字連接的Handler等等,此處不做進一步分析。

相關閱讀:
Netty源碼愫讀(一)ByteBuf相關源碼學習 【http://www.lxweimin.com/p/016daa404957
Netty源碼愫讀(二)Channel相關源碼學習【http://www.lxweimin.com/p/02eac974258e
Netty源碼愫讀(三)ChannelPipeline、ChannelHandlerContext相關源碼學習【http://www.lxweimin.com/p/be82d0fcdbcc
Netty源碼愫讀(五)EventLoop與EventLoopGroup相關源碼學習【http://www.lxweimin.com/p/05096995d296
Netty源碼愫讀(六)ServerBootstrap相關源碼學習【http://www.lxweimin.com/p/a71a9a0291f3

參考書籍:
《Netty實戰》
《Netty權威指南》

參考博客:

http://www.lxweimin.com/p/4c35541eec10
http://www.lxweimin.com/p/0b79872eb515
http://www.lxweimin.com/p/a0a51fd79f62
http://www.wolfbe.com/detail/201609/379.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容