1. 程式人生 > >Netty 原始碼 ChannelHandler(四)編解碼技術

Netty 原始碼 ChannelHandler(四)編解碼技術

Netty 原始碼 ChannelHandler(四)編解碼技術

Netty 系列目錄(https://www.cnblogs.com/binarylei/p/10117436.html)

一、拆包與粘包問題

由於 TCP 是面向位元組流的,什麼意思呢:雖然應用程式和 TCP 的互動是一次一個資料塊(大小不等),但 TCP 把應用程式交下來的資料僅僅看成式一連串的無結構的位元組流。TCP 並不知道所傳送的位元組流的含義。

因此 TCP 不保證接收方應用程式所收到的資料塊和傳送方應用程式所發出的資料塊具有對應大小的關係(例如,傳送方應用程式交給傳送方的 TCP 共 10 個數據塊,但接收方的 TCP 可能只用了 4 個就把收到的位元組流交付上層的應用程式)。

同時,TCP 不關心應用程序一次把多長的報文傳送到 TCP 的 快取 中,而是根據對方給出的視窗值和當前網路阻塞的程度來決定一個報文段應包含多少個位元組(UDP 傳送的報文長度是應用程序給出的)。如果應用程序傳送到 TCP 快取的資料塊太長,TCP 就可以把他劃分短一點再傳送。如果應用程式一次只發來一個位元組,TCP 也可以等待積累有足夠多的位元組後再構成報文段傳送出去。

TCP 傳送報文一般是 3 個時機:

  • 緩衝區資料達到 最大報文長度 MSS;
  • 由傳送端的應用程序指明要求傳送報文段,即 TCP 支援的推送(push)操作;
  • 當傳送方的一個計時器期限到了,即使長度不超過 MSS ,也傳送。

以上引自《計算機網路-----謝希仁》。

說了這麼多,TCP 的這種機制,會導致什麼問題呢?粘包問題。有了粘包,就需要拆包。

如何區分一個整包訊息,業界通常有如下 4 種做法:

  1. 固定長度,例如每 120 個位元組代表一個整包訊息,不足的前面補零。解碼器在處理這類定常訊息的時候比較簡單,每次讀到指定長度的位元組後再進行解碼。

  2. 通過回車換行符區分訊息,例如 FTP 協議。這類區分訊息的方式多用於文字協議。

  3. 通過分隔符區分整包訊息。

  4. 通過指定長度來標識整包訊息。

Netty 作為一個網路框架,對 TCP 連線中的問題都做了全面的考慮,比如粘包拆包導致的半包問題,如何編解碼,如何實現私有協議,序列化等等。本文主要針對這些問題做一個簡單介紹,目的是想對整個 Netty 的編解碼框架做一個全盤的審視,以確保在後面的原始碼學習中不會一葉障目不見泰山。

二、基於長度編解碼器的具體實現

基於長度的實現有 2 個現成的類:

  • FixedLengthFrameDecoder 基於建構函式中的固定長度。
    該類很簡單,構造方法中,傳入一個整數,該解碼器就會按照這個數字對累積區的位元組進行切分。

  • LengthFieldBasedFrameDecoder 基於流中動態的長度
    該類比較複雜。建構函式引數多達 6 個,在構建私有協議棧時大有用處。

三、基於分割符的編解碼器

同樣有 2 個:

  • DelimiterBasedFrameDecoder 使用者提供分割符。
    該類比較簡單,根據使用者提供的分割符對累積區的內容進行分割。效能相對不是那麼完美。

  • LineBasedFrameDecoder 基於換行符,支援多種換行符 \n \r\n 速度相比自定義較快。
    該類使用更簡單,根據換行符進行拆包粘包。

四、google 的 ProtobufDecoder ProtobufEncoder 序列化介紹

Netty 中有很多序列化工具,比如 Jboss 的 Marshalling,同時也支援 Java 標準的序列化。 但我們重點關注 google 的 protobuf 庫。因為它的效能最高。

上面的 4 個解碼器都是基於 ByteToMessageDecoder,將粘包的位元組轉為使用者需要的位元組。而 ProtobufDecoder 不是繼承自 ByteToMessageDecoder,而是繼承自 MessageToMessageDecoder,名字都不同。MessageToMessageDecoder 的作用是什麼呢?

從名字上看,該類用於將兩個訊息進行轉換(比如一種 POJO 轉成另一種)。後面我們將花大篇幅講述這個類庫。

五、其他的

(1) TooLongFrameException

由於 Netty 是一個非同步框架,所以需要在位元組可以解碼之前在記憶體中緩衝他們。因此不能讓解碼器緩衝大量的資料以至於耗盡可用的記憶體。為了解決這個問題,Netty 提供了 TooLongFrameException 類,其將由解碼器在幀超出指定的大小限制時丟擲異常。

你可以設定一個最大的閾值,當超過該閾值,這丟擲異常。

(2) 寫大型資料的 FileRegion

有時候你可能需要寫一個大型的資料,如果不停的寫入,可能導致 OOM,所以在寫大型資料時,需要準備好處理到遠端節點的連線時慢速連線的情況,這種情況會導致記憶體釋放的延遲。

我們可以使用 NIO 的零拷貝特性,這種特性消除了將檔案內容從檔案系統移動到網路棧的複製過程。而我們所需要做的就是使用一個 FileRegion 介面的實現。

官方定義:

通過支援零拷貝的檔案傳輸的 Channel 來發送的檔案區域。

六、總結

本文並沒有刨析原始碼,主要是針對 Netty 中現有的或者設計的編解碼,序列化等工具做一個介紹,方便後面有條不紊的按照這個路線研究他們的具體實現。

參考:

  1. 《Netty 粘包 & 拆包 & 編碼 & 解碼 & 序列化 介紹》:http://www.cnblogs.com/stateis0/p/9062162.html

每天用心記錄一點點。內容也許不重要,但習慣很重要!