Netty源碼05-Netty編解碼器框架

TCP粘包/半包問題

TCP是以流的方式來處理數據,一個完整的包可能會被TCP拆分紅多個包進行發送,也可能把小的封裝成一個大的數據包發送

TCP粘包/分包的緣由

  • 應用程序寫入的字節大小大於套接字發送緩衝區的大小,會發生拆包現象* 應用程序寫入數據小於套接字緩衝區大小,網卡將應用屢次寫入的數據發送到網絡上,這將會發生粘包現象
進行MSS大小的TCP分段,當TCP報文長度-TCP頭部長度 > MSS的時候將發生拆包
以太網幀的payload(淨荷)大於MTU(1500字節)進行ip分片

image.png

TCP粘包/分包解決方法

主要有四種方法:java

  • 消息定長:FixedLengthFrameDecoder
  • 行分隔符類: LineBasedFrameDecoder
  • 定義分隔符類:DelimiterBasedFrameDecoder
  • 將消息分爲消息頭和消息體:LengthFieldBasedFrameDecoder類。分爲有頭部的拆包與粘包、長度字段在前且有頭部的拆包與粘包、多擴展頭部的拆包與粘包
底層Socket通訊常常遇到這種基礎打解包的需求,我寫了一個針對該場景的通用信息交換平臺,能夠實現打解包的配置化,放在Github上

GitHub 地址:https://github.com/dragonflysun2019/GXPMastergit

編解碼器框架

什麼是編解碼器

每一個網絡應用程序都必須定義如何解析在兩個節點之間來回傳輸的原始字節,以及如何將其和目標應用程序的數據格式作相互轉換。這種轉換邏輯由Codec處理,CodecEncoderDecoder組成,它們每種均可以將字節流從一種格式轉換爲另外一種格式。那麼它們的區別是什麼呢?github

若是將消息看做是對於特定的應用程序具備具體含義的結構化的字節序列—它的數據。那麼Encoder是將消息轉換爲適合於傳輸的格式(最有可能的就是字節流);而對應的Decoder則是將網絡字節流轉換回應用程序的消息格式。所以,Encoder操做出站數據,而Decoder處理入站數據。咱們前面所學的解決粘包半包的其實也是編解碼器框架的一部分。網絡

解碼器

  • 將字節解碼爲消息:ByteToMessageDecoder
  • 將一種消息類型解碼爲另外一種:MessageToMessageDecoder
由於解碼器是負責將入站數據從一種格式轉換到另外一種格式的,因此 Netty 的解碼器實現了 ChannelInboundHandler

何時會用到解碼器呢?框架

很簡單:每當須要爲 ChannelPipeline 中的下一個 ChannelInboundHandler 轉換入站數據時會用到。此外,得益於 ChannelPipeline 的設計,能夠將多個解碼器連接在一塊兒,以實現任意複雜的轉換邏輯

將字節解碼爲消息ByteToMessageDecoder

抽象類ByteToMessageDecoder異步

將字節解碼爲消息(或者另外一個字節序列)是一項如此常見的任務,以致於Netty 爲它提供了一個抽象的基類:ByteToMessageDecoder。因爲你不可能知道遠程節點是否會一次性地發送一個完整的消息,因此這個類會對入站數據進行緩衝,直到它準備好處理學習

它最重要方法編碼

decode(ChannelHandlerContext ctx,ByteBuf in,List<Object> out)

這是你必須實現的惟一抽象方法。decode()方法被調用時將會傳入一個包含了傳入數據的ByteBuf,以及一個用來添加解碼消息的List。對這個方法的調用將會重複進行,直到肯定沒有新的元素被添加到該List,或者該ByteBuf 中沒有更多可讀取的字節時爲止。而後,若是該List 不爲空,那麼它的內容將會被傳遞給ChannelPipeline 中的下一個ChannelInboundHandlerspa

將一種消息類型解碼爲另外一種MessageToMessageDecoder<T>

在兩個消息格式之間進行轉換(例如,從String->Integer設計

decode(ChannelHandlerContext ctx,I msg,List<Object> out)

對於每一個須要被解碼爲另外一種格式的入站消息來講,該方法都將會被調用。解碼消息隨後會被傳遞給ChannelPipeline中的下一個ChannelInboundHandler

MessageToMessageDecoder<T>,T表明源數據的類型

TooLongFrameException

因爲Netty 是一個異步框架,因此須要在字節能夠解碼以前在內存中緩衝它們(沒有讀取完以前是沒法解碼的)。所以,不能讓解碼器緩衝大量的數據以致於耗盡可用的內存。爲了解除這個常見的顧慮,Netty 提供了TooLongFrameException 類,其將由解碼器在幀超出指定的大小限制時拋出

爲了不這種狀況,你能夠設置一個最大字節數的閾值,若是超出該閾值,則會致使拋出一個TooLongFrameException(隨後會被ChannelHandler.exceptionCaught()方法捕獲)。而後,如何處理該異常則徹底取決於該解碼器的用戶。某些協議(如HTTP)可能容許你返回一個特殊的響應。而在其餘的狀況下,惟一的選擇可能就是關閉對應的鏈接

編碼器

和解碼器的功能正好相反。Netty 提供了一組類,用於幫助你編寫具備如下功能的編碼器:

  • 將消息編碼爲字節:MessageToByteEncoder
  • 將消息編碼爲消息:MessageToMessageEncoder<T>,T表明源數據的類型

將消息編碼爲字節MessageToByteEncoder<T>

encode(ChannelHandlerContext ctx,I msg,ByteBuf out)

encode()方法是你須要實現的惟一抽象方法。它被調用時將會傳入要被該類編碼爲ByteBuf 的(類型爲I 的)出站消息。該ByteBuf 隨後將會被轉發給ChannelPipeline中的下一個ChannelOutboundHandler

將消息編碼爲消息MessageToMessageEncoder<T>

encode(ChannelHandlerContext ctx,I msg,List<Object> out)

這是你須要實現的惟一方法。每一個經過write()方法寫入的消息都將會被傳遞給encode()方法,以編碼爲一個或者多個出站消息。隨後,這些出站消息將會被轉發給ChannelPipeline中的下一個ChannelOutboundHandler

編解碼器類

咱們一直將解碼器和編碼器做爲單獨的實體討論,可是你有時將會發如今同一個類中管理入站和出站數據和消息的轉換是頗有用的。Netty 的抽象編解碼器類正好用於這個目的,由於它們每一個都將捆綁一個解碼器/編碼器對,以處理咱們一直在學習的這兩種類型的操做。這些類同時實現了ChannelInboundHandler 和ChannelOutboundHandler 接口。

爲何咱們並無一直優先於單獨的解碼器和編碼器使用這些複合類呢?由於經過儘量地將這兩種功能分開,最大化了代碼的可重用性和可擴展性,這是Netty 設計的一個基本原則。

相關的類:

  • 抽象類ByteToMessageCodec<T>
  • 抽象類MessageToMessageCodec<INBOUND_IN, OUTBOUND_IN>
  • HttpServerCodec
  • HttpClientCodec
相關文章
相關標籤/搜索