目錄:
- 粘包 & 拆包及解決方案 ByteToMessageDecoder
- 基于長度編解碼器
- 基于分割符的編解碼器
- google 的 Protobuf 序列化介紹
- 其他的
前言
Netty 作為一個(gè)網(wǎng)絡(luò)框架筐高,對 TCP 連接中的問題都做了全面的考慮城瞎,比如粘包拆包導(dǎo)致的半包問題,如何編解碼,如何實(shí)現(xiàn)私有協(xié)議,序列化等等。本文主要針對這些問題做一個(gè)簡單介紹募判,目的是想對整個(gè) Netty 的編解碼框架做一個(gè)全盤的審視,以確保在后面的源碼學(xué)習(xí)中不會一葉障目不見泰山咒唆。
1. 粘包 & 拆包及解決方案 ByteToMessageDecoder
由于TCP是面向字節(jié)流的届垫,什么意思呢:雖然應(yīng)用程序和 TCP 的交互是一次一個(gè)數(shù)據(jù)塊(大小不等),但 TCP 把應(yīng)用程序交下來的數(shù)據(jù)僅僅看成式一連串的無結(jié)構(gòu)的字節(jié)流全释。TCP 并不知道所傳送的字節(jié)流的含義装处。
因此 TCP 不保證接收方應(yīng)用程序所收到的數(shù)據(jù)塊和發(fā)送方應(yīng)用程序所發(fā)出的數(shù)據(jù)塊具有對應(yīng)大小的關(guān)系(例如,發(fā)送方應(yīng)用程序交給發(fā)送方的 TCP 共 10 個(gè)數(shù)據(jù)塊浸船,但接收方的 TCP 可能只用了 4 個(gè)就把收到的字節(jié)流交付上層的應(yīng)用程序)妄迁。
同時(shí),TCP 不關(guān)心應(yīng)用進(jìn)程一次把多長的報(bào)文發(fā)送到 TCP 的 緩存 中李命,而是根據(jù)對方給出的窗口值和當(dāng)前網(wǎng)絡(luò)阻塞的程度來決定一個(gè)報(bào)文段應(yīng)包含多少個(gè)字節(jié)(UDP 發(fā)送的報(bào)文長度是應(yīng)用進(jìn)程給出的)登淘。如果應(yīng)用進(jìn)程傳送到 TCP 緩存的數(shù)據(jù)塊太長,TCP 就可以把他劃分短一點(diǎn)再傳送封字。如果應(yīng)用程序一次只發(fā)來一個(gè)字節(jié)黔州,TCP 也可以等待積累有足夠多的字節(jié)后再構(gòu)成報(bào)文段發(fā)送出去。
- TCP 發(fā)送報(bào)文一般是 3 個(gè)時(shí)機(jī):
- 緩沖區(qū)數(shù)據(jù)達(dá)到
最大報(bào)文長度 MSS
阔籽; - 由發(fā)送端的應(yīng)用進(jìn)程指明要求發(fā)送報(bào)文段流妻,即 TCP 支持的推送(push)操作;
- 當(dāng)發(fā)送方的一個(gè)計(jì)時(shí)器期限到了笆制,即使長度不超過 MSS 绅这,也發(fā)送。
以上引自《計(jì)算機(jī)網(wǎng)絡(luò)-----謝希仁》在辆。
說了這么多证薇,TCP 的這種機(jī)制度苔,會導(dǎo)致什么問題呢?粘包問題棕叫。有了粘包林螃,就需要拆包奕删。
- 一般解決粘包拆包問題有 4 種辦法:
- 固定數(shù)據(jù)的長度俺泣,比如 100 字節(jié),如果不夠就補(bǔ)空格完残。
- 學(xué)習(xí) HTTP 伏钠,F(xiàn)TP 等,使用回車換行符號谨设。
- 將消息分為 head 和 body熟掂,head 中包含 body 長度的字段,一般 head 的第一個(gè)字段使用 int 值來表示 body 長度扎拣。
- 使用更復(fù)雜的應(yīng)用層協(xié)議(等于沒說 =_= !)赴肚。
Netty 作為一個(gè)網(wǎng)絡(luò)框架,直接和 TCP 打交道二蓝,自然考慮了這個(gè)問題誉券。而解決這個(gè)問題的主要實(shí)現(xiàn)就是抽象類 ByteToMessageDecoder,詳見 Netty 解碼器抽象父類 ByteToMessageDecoder 源碼解析刊愚。Netty 使用了模板設(shè)計(jì)模式踊跟,這個(gè)類只定義了共有行為,具體解碼實(shí)現(xiàn)還是子類鸥诽,比如上面提到的 4 種方式商玫。
2. 基于長度編解碼器的具體實(shí)現(xiàn)
基于長度的實(shí)現(xiàn)有2個(gè)現(xiàn)成的類:
FixedLengthFrameDecoder 基于構(gòu)造函數(shù)中的固定長度
該類很簡單,構(gòu)造方法中牡借,傳入一個(gè)整數(shù)拳昌,該解碼器就會按照這個(gè)數(shù)字對累積區(qū)的字節(jié)進(jìn)行切分。LengthFieldBasedFrameDecoder 基于流中動態(tài)的長度
該類比較復(fù)雜钠龙。構(gòu)造函數(shù)參數(shù)多達(dá) 6 個(gè)炬藤,在構(gòu)建私有協(xié)議棧時(shí)大有用處。
3. 基于分割符的編解碼器
同樣有 2 個(gè):
DelimiterBasedFrameDecoder 用戶提供分割符俊鱼。
該類比較簡單刻像,根據(jù)用戶提供的分割符對累積區(qū)的內(nèi)容進(jìn)行分割。性能相對不是那么完美并闲。LineBasedFrameDecoder 基于換行符,支持多種換行符 \n \r\n 速度相比自定義較快细睡。
該類使用更簡單,根據(jù)換行符進(jìn)行拆包粘包帝火。
4. google 的 ProtobufDecoder ProtobufEncoder 序列化介紹
Netty 中有很多序列化工具溜徙,比如 Jboss 的 Marshalling湃缎,同時(shí)也支持 Java 標(biāo)準(zhǔn)的序列化。 但我們重點(diǎn)關(guān)注 google 的 protobuf 庫蠢壹。因?yàn)樗男阅茏罡摺?/p>
上面的 4 個(gè)解碼器都是基于 ByteToMessageDecoder嗓违,將粘包的字節(jié)轉(zhuǎn)為用戶需要的字節(jié)。而ProtobufDecoder 不是繼承自 ByteToMessageDecoder图贸,而是繼承自 MessageToMessageDecoder蹂季,名字都不同。MessageToMessageDecoder 的作用是什么呢疏日?
從名字上看偿洁,該類用于將兩個(gè)消息進(jìn)行轉(zhuǎn)換(比如一種 POJO 轉(zhuǎn)成另一種)。后面我們將花大篇幅講述這個(gè)類庫沟优。
5. 其他的
1. TooLongFrameException
由于 Netty 是一個(gè)異步框架涕滋,所以需要在字節(jié)可以解碼之前在內(nèi)存中緩沖他們。因此不能讓解碼器緩沖大量的數(shù)據(jù)以至于耗盡可用的內(nèi)存挠阁。為了解決這個(gè)問題宾肺,Netty 提供了 TooLongFrameException 類,其將由解碼器在幀超出指定的大小限制時(shí)拋出異常侵俗。
你可以設(shè)置一個(gè)最大的閾值锨用,當(dāng)超過該閾值,這拋出異常坡慌。
2. 寫大型數(shù)據(jù)的 FileRegion
有時(shí)候你可能需要寫一個(gè)大型的數(shù)據(jù)黔酥,如果不停的寫入,可能導(dǎo)致 OOM洪橘,所以在寫大型數(shù)據(jù)時(shí)跪者,需要準(zhǔn)備好處理到遠(yuǎn)程節(jié)點(diǎn)的連接時(shí)慢速連接的情況,這種情況會導(dǎo)致內(nèi)存釋放的延遲熄求。
我們可以使用 NIO 的零拷貝特性渣玲,這種特性消除了將文件內(nèi)容從文件系統(tǒng)移動到網(wǎng)絡(luò)棧的復(fù)制過程。而我們所需要做的就是使用一個(gè) FileRegion 接口的實(shí)現(xiàn)弟晚。
官方定義:
通過支持零拷貝的文件傳輸?shù)?Channel 來發(fā)送的文件區(qū)域忘衍。
6. 總結(jié)
本文并沒有刨析源碼,主要是針對 Netty 中現(xiàn)有的或者設(shè)計(jì)的編解碼卿城,序列化等工具做一個(gè)介紹枚钓,方便后面有條不紊的按照這個(gè)路線研究他們的具體實(shí)現(xiàn)。
good luckI骸2蠼荨!!