天天看點

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

0 什麼是編解碼器

每個網絡應用程式都必須定義

  • 如何解析在兩個節點之間來回傳輸的原始位元組
  • 如何将其和目标應用程式的資料格式做互相轉換

這種轉換邏輯由編解碼器處理,編解碼器由編碼器和解碼器組成,它們每種都可以将位元組流從一種格式轉換為另一種格式

那麼它們的差別是什麼呢?

如果将消息看作是對于特定的應用程式具有具體含義的結構化的位元組序列— 它的資料。那 麼編碼器是将消息轉換為适合于傳輸的格式(最有可能的就是位元組流);而對應的解碼器則是将 網絡位元組流轉換回應用程式的消息格式。是以,編碼器操作出站資料,而解碼器處理入站資料。

記住這些背景資訊,接下來讓我們研究一下 Netty 所提供的用于實作這兩種元件的類。

1 Netty解碼概述

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

1.1 本文目标

  • 解碼器抽象的解碼過程
  • Netty裡面有哪些拆箱即用的解碼器

Netty 的解碼器類:

  • 将位元組解碼為消息

    ByteToMessageDecoder 和 ReplayingDecoder

  • 将一種消息類型解碼為另一種

    MessageToMessageDecoder

解碼器負責

将入站資料從一種格式轉到另一種

,是以 Netty 解碼器實

現了 

ChannelInboundHandler

 也很自然。

  • 什麼時候會用解碼器?

    每當需為

    ChannelPipeline

    中的下一個

    ChannelInboundHandler

    轉換入站資料時。

得益于

ChannelPipeline

 的設計,可以将多個解碼器連接配接在一起,以實作任意複雜的轉換邏輯,這也是 Netty 是如何支援代碼的子產品化以及複用的一個很好的例子。

案例代碼

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器
Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器
Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

2 抽象解碼器 ByteToMessageDecoder

2.1 示例

Netty 提供抽象基類:ByteToMessageDecoder,将位元組解碼為消息(或另一個位元組序列)。

由于

你不可能知道遠端節點是否會一次性發送一個完整消息

,是以該類會

緩沖入站資料

,直到它準備好處理。

ByteToMessageDecoderAPI

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

假設你接收了一個包含簡單 int 的位元組流,每個 int 都需要被單獨處理

在這種情況下,你需要從入站ByteBuf中讀取每個 int,并将它傳遞給ChannelPipeline 中的下一個 ChannelInboundHandler

為了解碼這個位元組流,你要擴充 ByteToMessageDecoder類(原子類型的 int 在被添加到 List 中時,會被自動裝箱為 Integer)

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

每次從入站 ByteBuf 中讀取 4 位元組,将其解碼為一個 int,然後将它添加到一個 List 中

當沒有更多的元素可以被添加到該 List 中時,它的内容将會被發送給下一個 Channel- InboundHandler

ToIntegerDecoder類擴充了ByteToMessageDecoder

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

雖然ByteToMessageDecoder可以很簡單地實作這種模式,但是你可能會發現,在調用 readInt()前不得不驗證所輸入的 ByteBuf 是否具有足夠的資料有點繁瑣

在下一節中, 我們将讨論 ReplayingDecoder,它是一個特殊的解碼器,以少量的開銷消除了這個步驟

2.2 源碼解析

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

下面開始解析解碼流程的源碼:

2.2.1 累加位元組流

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

其中的

cumulator

 為

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

看一下這個

MERGE_CUMULATOR

public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
    
    @Override
    public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
        ByteBuf buffer;
        // 目前寫指針後移一定位元組,若超過最大容量,則擴容
        if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
                || cumulation.refCnt() > 1) {
            // 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().
            //
            // 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;
    }
};
      

2.2.2 調用子類 decode 方法進行解析

進入該方法檢視源碼

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器
Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

2.2.2 将解析到的 ByteBuf 向下傳播

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

注意到上圖中的如下代碼段:

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器
Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

編解碼器中的引用計數

對于編碼器和解碼器,一旦消息被編碼或解碼,它就會被 ReferenceCountUtil.release(message)調用自動釋放。

若需要保留引用以便稍後使用,可調用 ReferenceCountUtil.retain(message),這會增加該引用計數,進而防止該消息被釋放。

3 固定長度解碼器

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

4 行解碼器

4.1 定位行尾

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器
Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

4.2 非丢棄模式

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器
Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

找到換行符

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

找不到換行符

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器
Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

4.3 丢棄模式

Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器
Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器
Netty源碼面試解析(八) - 解碼上0 什麼是編解碼器1 Netty解碼概述2 抽象解碼器 ByteToMessageDecoder3 固定長度解碼器4 行解碼器

參考

  • 《Netty實戰》

繼續閱讀