日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Netty常用招式—ChannelHandler與編解碼

上一篇文章我們深入學習了Netty邏輯架構中的核心組件ChannelHandler和ChannelPipeline,并介紹了它在日常開發(fā)使用中的最佳實踐。文中也提到了,ChannelHandler主要用于數(shù)據(jù)輸入、輸出過程中的加工處理,比如編解碼、異常處理等。

創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設、高性價比惠陽網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式惠陽網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設找我們,業(yè)務覆蓋惠陽地區(qū)。費用合理售后完善,十余年實體公司更值得信賴。

今天,我們就選取日常開發(fā)中最常用的一種ChannelHandler用途來學習——編解碼器。

如果說ChannelHandler的學習是Netty的基礎招式,那么編解碼就是“基礎招式”中衍生出的“常用招式“,我們往往會以一個ChannelHandler來實現(xiàn)編解碼邏輯。無論是網(wǎng)絡編程實戰(zhàn),還是面試八股文,都離不開編解碼的知識。

本文預計閱讀時間約 15分鐘,

將重點圍繞以下幾個問題展開:

  • 學習編解碼器,從粘包/拆包開始
  • 如何實現(xiàn)自定義編解碼器
  • Netty有哪些開箱即用的編解碼器

1、學習編解碼器,從粘包/拆包開始

1.1 為什么會有粘包/拆包粘包/拆包

問題,相信大家都有所耳聞,這個問題的出現(xiàn)主要包括三個原因:

1)MTU 和 MSS 限制

MTU(Maxitum Transmission Unit) 是OSI五層網(wǎng)絡模型中 數(shù)據(jù)鏈路層 對一次可以發(fā)送的最大數(shù)據(jù)的限制,一般來說大小為 1500 byte。

MSS(Maximum Segement Size) 是指 TCP報文中data部分的最大長度,它是傳輸層一次發(fā)送最大數(shù)據(jù)的大小限制。

MSS和MTU的關系如下所示:

 
 
 
 
  1. MSS長度=MTU長度 - IP Header - TCP Header 

因此,當 MSS長度 + IP Header + TCP Header > MTU長度 時,就需要拆分多個報文進行發(fā)送,會導致“拆包”現(xiàn)象。

2)TCP滑動窗口

TCP的流量控制方法就是“滑動窗口”。當A向B發(fā)送數(shù)據(jù)時,B作為接收端會告知發(fā)送端A自己可以接受的窗口數(shù)值,以此來控制A的發(fā)送流量大小,從而達到流量控制的目的。

假設接收方B告知發(fā)送方A的窗口大小為256,意味著發(fā)送方最多還可以發(fā)送256個字節(jié),而由于發(fā)送方的數(shù)據(jù)大小是518字節(jié),因此只能發(fā)送前256字節(jié),等到接收方ack后,才能發(fā)送剩余字節(jié)。會導致“拆包”現(xiàn)象。

3)Nagle算法

TCP/IP協(xié)議中,無論發(fā)送多少大小的數(shù)據(jù),都要在數(shù)據(jù)(DATA)前面加上協(xié)議頭(TCP Header + IP Header)。如果每次需要發(fā)送的數(shù)據(jù)只有 1 字節(jié),加上 20 個字節(jié) IP Header 和 20 個字節(jié) TCP Header,每次發(fā)送的數(shù)據(jù)包大小為 41 字節(jié),但真正有效的信息只有1個字節(jié),這就造成了非常大的浪費。

因此,TCP/IP中使用Nagle 算法來提高效率。

Nagle 算法核心思想在于“化零為整“。它是在數(shù)據(jù)未得到確認之前先寫入緩沖區(qū),等待數(shù)據(jù)確認或者緩沖區(qū)積攢到一定大小再把數(shù)據(jù)包發(fā)送出去。

多個小數(shù)據(jù)包合并后一起發(fā)送出去,就造成了粘包。

1.2 怎么處理粘包/拆包

對于TCP,其實我們都知道它的一個特點就是“面向字節(jié)流”的傳輸協(xié)議,本身并沒有數(shù)據(jù)包的界限。所以不管什么原因造成了“粘包/拆包”,TCP協(xié)議本身的數(shù)據(jù)傳輸是可靠且正確的。

我們首先要明確一點:“粘包/拆包”導致的問題,本質(zhì)上是應用層的數(shù)據(jù)解析問題。

因此,解決拆包/粘包問題的核心方法:定義應用層的通信協(xié)議。

核心在于定義正確的數(shù)據(jù)邊界。

常見協(xié)議的解決方案包括三種:

1)固定長度

每個數(shù)據(jù)報文都約定一個固定的長度。

當接收方累計讀取到固定長度的報文后,就認為已經(jīng)獲得一個完整的消息。

比如我們要發(fā)送一個ABCDEFGHIJKLM的消息,約定固定消息長度為4,那么接收方就可以按照4的長度來解析。如下所示。

當發(fā)送方的數(shù)據(jù)小于固定長度時,比如最后一個數(shù)據(jù)包,只有MN兩個字符,這時候就需要空位補齊。

這種方案非常簡單,但是缺點也非常明顯,非常不靈活。

如果固定長度定義太長,就會浪費數(shù)據(jù)傳輸空間。如果定義太短,就會影響正確的數(shù)據(jù)傳輸。

這種方法一般不采用。

2)特定分隔符

除了固定長度外,我們比較容易想到的區(qū)分“數(shù)據(jù)邊界”的方法,就是用“特定分隔符”。當接收方讀到特定的分隔符,就認為拿到了一個完整的消息。

比如我們使用換行符 \n 來區(qū)分。

 
 
 
 
  1. AB\nCDEFG\nHIJK\nLMN\n 

這種方法就比較靈活了,適應不同長度的消息。但是,必須要注意,“特殊分隔符”不能和消息內(nèi)容重復,否則就會解析失敗了。

因此,我們在實踐過程中,可以考慮把消息進行編碼(如base64),然后用編碼字符集之外的符號作為“特定分隔符”。

這種方案一般用在協(xié)議比較簡單的場景中。

3)消息長度+內(nèi)容

一般項目開發(fā)中,最通用的方式還是采用 消息長度+內(nèi)容 的方式進行處理。

比如定義一個這樣的消息格式:

以這樣一個格式存儲,消息接收方在解析時,先讀取4字節(jié)長度的信息作為”消息長度“,這里是3,表示消息長度為3字節(jié)。然后就讀取3字節(jié)的消息內(nèi)容作為 完整 的消息。

舉個例子:

 
 
 
 
  1. 2AB5CDEFG4HIJK3LMN 

消息長度+內(nèi)容 的方式非常靈活,可以應用于各種場景中。

注意,在消息頭中,除了定義消息長度外,還可以自定義其他擴展字段,比如消息版本、算法類型等。

2、如何在Netty中實現(xiàn)自定義編解碼器

上面我們了解了出現(xiàn)“粘包/拆包”的原因以及常用的解決方法。下面看看如何在Netty中實現(xiàn)自定義編解碼器。

Netty作為一個優(yōu)秀的網(wǎng)絡通信框架,已經(jīng)提供了非常豐富的處理編解碼的抽象類,我們只需要自定義編解碼算法擴展即可。

2.1 自定義編碼器

我們先來看看自定義編碼器。因為編碼器比較簡單,不需要關注「粘包/拆包問題」。

常用的編碼抽象類包括MessageToByteEncoder 和 MessageToMessageEncoder,繼承自

ChannelOutboundHandlerAdapter,操作的是Outbound相關數(shù)據(jù)。

1)MessageToByteEncoder

這個編碼器用于消息對象編碼成字節(jié)流。它提供了encode的抽象方法,我們只需要實現(xiàn)encode方法,就能進行自定義編碼了。

編碼器實現(xiàn)非常簡單,不需要關注拆包/粘包問題。

我們舉一個栗子,將String類型消息轉(zhuǎn)換為字節(jié)流:

2)MessageToMessageEncoder

這個編碼器用于將一種消息對象編碼成另一種消息對象。這里的第二個Message可以理解為任意一個對象。如果是使用ByteBuf對象的話,就和上面的MessageToByteEncoder是一樣的了。

我們找一個Netty自帶的栗子看看,StringEncoder:

2.2 自定義解碼器

解碼器比編碼器要復雜一些,因為需要考慮“拆包/粘包”問題。

由于接收方有可能沒有接收到完整的消息,所以解碼框架需要對入站的數(shù)據(jù)做緩沖操作,直至獲取到完整的消息。

常用的解碼器抽象類包括 ByteToMessageDecoder 和 MessageToMessageDecoder,繼承自

ChannelInboundHandlerAdapter,操作的是Inbbound相關數(shù)據(jù)。

一般通用的做法是使用 ByteToMessageDecoder 解析 TCP 協(xié)議,解決拆包/粘包問題。解析得到有效的 ByteBuf 數(shù)據(jù),然后傳遞給后續(xù)的 MessageToMessageDecoder 做數(shù)據(jù)對象的轉(zhuǎn)換。

1)ByteToMessageDecoder

ByteToMessageDecoder解碼器用于字節(jié)流解碼成消息對象。

拿上面的“固定長度法”解決“粘包/拆包”舉一個栗子,Netty自帶的FixedLengthFrameDecoder。

通過固定長度frameLength,來對消息進行解析。

生產(chǎn)實踐中,可能會使用更加復雜的協(xié)議來實現(xiàn)自定義編解碼,比如protobuf。

2)MessageToMessageDecoder

MessageToMessageDecoder解碼器用于將一種消息對象解碼成另一種消息對象。如果你需要對解析后的字節(jié)數(shù)據(jù)做對象模型的轉(zhuǎn)換,這時候便需要用到這個解碼器。

3、Netty有哪些開箱即用的解碼器

作為一個優(yōu)秀的網(wǎng)絡編程框架,Netty除了支持擴展自定義編解碼器外,還提供了非常豐富的開箱即用的編解碼器。尤其是針對我們上文1.2節(jié)中提過的三種解決「粘包/拆包問題」的方式,都有開箱即用的實現(xiàn)。

3.1 固定長度解碼器 FixedLengthFrameDecoder

這個解碼器上文已經(jīng)提到過,對應1.2節(jié)中的「固定長度解碼」,這里再稍微展開一下。

通過構造函數(shù)配置固定長度 frameLength,然后在decode時,按照frameLength 進行解碼。

  • 當讀取到長度大小為 frameLength 的消息,那么解碼器認為已經(jīng)獲取到了一個完整的消息。
  • 當消息長度小于 frameLength,F(xiàn)ixedLengthFrameDecoder 解碼器會一直等后續(xù)數(shù)據(jù)包的到達,直至獲得完整的消息。

3.2 特殊分隔符解碼器 DelimiterBasedFrameDecoder

這個解碼器對應1.2節(jié)中的「特殊分隔符解碼」,也是一個繼承自ByteToMessageDecoder的解碼器。

這個解碼器會使用 1個 或 多個 符號delimiter 對傳入的消息(ByteBuf)進行解碼。

我們看一下構造器,了解一下幾個重要參數(shù)。

  • maxFranmeLength

maxFranmeLength 是待處理消息的最大長度限制。如果超過 maxFranmeLength 還沒有檢測到指定分隔符,將會拋出 TooLongFrameException。

  • stripDelimiter

stripDelimiter是一個boolean類型, 用于判斷解碼后得到的消息是否移除分隔符。如果 stripDelimiter=false,那么解碼后的消息內(nèi)容就會保留分隔符信息。

  • failFast

failFast是一個boolean類型。如果為true,那么消息在超出 maxFranmeLength 后,會立即拋出 TooLongFrameException。如果為false,那么會等到解碼出一個完整的消息后才會拋出TooLongFrameException。

  • delimiters

delimiters 的類型是 ByteBuf 數(shù)組,可以在構造器中同時傳入多個分隔符,但是在解析時,最終會選擇長度最短的分隔符進行消息拆分。

例如收到的數(shù)據(jù)為:

 
 
 
 
  1. ABCD\nEFG\r\n  

如果指定的分隔符為 \n 和 \r\n,那么會解碼出兩個消息。

 
 
 
 
  1. ABCD   EFG 

如果指定的特定分隔符只有 \r\n,那么只會解碼出一個消息:

 
 
 
 
  1. ABCD\nEFG 

3.3 長度域解碼器 LengthFieldBasedFrameDecoder

這個解碼器是生產(chǎn)實踐中運用比較廣泛的一種(比如RocketMQ),相對復雜,但是特別靈活,基本能覆蓋各種基于長度進行拆包的方案,比如1.2節(jié)中提到的「消息長度+內(nèi)容」的方案。

使用這個解碼器的時候,重點需要了解4個參數(shù),掌握了參數(shù)的設置,就能快速實現(xiàn)不同的基于長度的拆包解碼方案。

1)解碼方案一:基于消息長度 + 消息內(nèi)容,解碼結果不截斷消息頭

報文只包含消息長度 Length 和消息內(nèi)容 Content 字段,其中 Length 為 16 進制表示,共占用 2 字節(jié),Length 的值 0x000C 代表 Content 占用 12 字節(jié)。

解碼示例:

2)解碼方案二:基于消息長度 + 消息內(nèi)容,解碼結果截斷

與方案一不同之處在于,解碼結果會截斷消息頭(跳過2字節(jié))

解碼示例:

3)解碼方案三:基于消息頭 + 消息長度 + 消息內(nèi)容

消息起始位置添加特殊消息頭,消息長度 Length字段 后移。

解碼示例:

4)解碼方案四:基于消息長度 + 消息頭 + 消息內(nèi)容

消息起始位置為消息長度 Length字段,后面并不直接添加 消息內(nèi)容,而是先添加 消息頭header,再添加 消息內(nèi)容。

解碼示例:

由于 Length 后面不是馬上添加content,所以需要加上 lengthAdjustment(2 字節(jié))才能得到 Header + Content 的內(nèi)容(14 字節(jié))。

4、小結

來簡單回顧下吧。

本文主要介紹了ChannelHandler的一種典型應用場景——編解碼器。

編解碼器核心關注點在于「粘包/拆包」的處理,我們介紹了「粘包/拆包」產(chǎn)生的原因以及常用解決方案。然后說明了如何使用Netty框架實現(xiàn)自定義編解碼器。

最后,介紹了Netty中非常好用的幾個開箱即用的編解碼器。


網(wǎng)頁題目:Netty常用招式—ChannelHandler與編解碼
網(wǎng)頁路徑:http://www.5511xx.com/article/dhehjdh.html