Netty之路(二)TCP拆包/粘包問題

TCP傳輸協(xié)議是面向流的咆贬,就是沒有界限的一串?dāng)?shù)據(jù)危融。TCP底層并不了解上層業(yè)務(wù)數(shù)據(jù)的具體含義,它會根據(jù)TCP緩沖區(qū)的實際情況進(jìn)行包的劃分擎析,所以在業(yè)務(wù)上認(rèn)為畦韭,一個完整的包可能會被TCP拆分成多個包就行發(fā)送湿颅,也有可能把多個小的包封裝成一個大的數(shù)據(jù)包發(fā)送艘虎,這就是所謂的TCP拆包和粘包問題斋射。

TCP拆包/粘包問題

image.png

假設(shè)客戶端分別發(fā)送了兩個數(shù)據(jù)包D1和D2給服務(wù)端症概,由于服務(wù)端一次讀取到字節(jié)數(shù)是不確定的蕾额,故可能存在以下四種情況:

服務(wù)端分兩次讀取到了兩個獨立的數(shù)據(jù)包,分別是D1和D2彼城,沒有粘包和拆包
服務(wù)端一次接受到了兩個數(shù)據(jù)包诅蝶,D1和D2粘合在一起,稱之為TCP粘包
服務(wù)端分兩次讀取到了數(shù)據(jù)包募壕,第一次讀取到了完整的D1包和D2包的部分內(nèi)容调炬,第二次讀取到了D2包的剩余內(nèi)容,這稱之為TCP拆包
服務(wù)端分兩次讀取到了數(shù)據(jù)包司抱,第一次讀取到了D1包的部分內(nèi)容D1_1筐眷,第二次讀取到了D1包的剩余部分內(nèi)容D1_2和完整的D2包。
特別要注意的是习柠,如果TCP的接受滑窗非常小匀谣,而數(shù)據(jù)包D1和D2比較大照棋,很有可能會發(fā)生第五種情況,即服務(wù)端分多次才能將D1和D2包完全接受武翎,期間發(fā)生多次拆包烈炭。

TCP拆包/粘包發(fā)生的原因

在網(wǎng)絡(luò)通信的過程中,每次可以發(fā)送的數(shù)據(jù)包大小是受多種因素限制的宝恶,如 MTU 傳輸單元大小符隙、MSS 最大分段大小、滑動窗口等垫毙。如果一次傳輸?shù)木W(wǎng)絡(luò)包數(shù)據(jù)大小超過傳輸單元大小霹疫,那么我們的數(shù)據(jù)可能會拆分為多個數(shù)據(jù)包發(fā)送出去。如果每次請求的網(wǎng)絡(luò)包數(shù)據(jù)都很小综芥,一共請求了 10000 次丽蝎,TCP 并不會分別發(fā)送 10000 次。因為 TCP 采用的 Nagle 算法對此作出了優(yōu)化膀藐。

MTU 最大傳輸單元和 MSS 最大分段大小

MTU(Maxitum Transmission Unit) 是鏈路層一次最大傳輸數(shù)據(jù)的大小屠阻。MTU 一般來說大小為 1500 byte。MSS(Maximum Segement Size) 是指 TCP 最大報文段長度额各,它是傳輸層一次發(fā)送最大數(shù)據(jù)的大小国觉。如下圖所示,MTU 和 MSS 一般的計算關(guān)系為:MSS = MTU - IP 首部 - TCP首部虾啦,如果 MSS + TCP 首部 + IP 首部 > MTU麻诀,那么數(shù)據(jù)包將會被拆分為多個發(fā)送。這就是拆包現(xiàn)象缸逃。


image.png

滑動窗口

滑動窗口是 TCP 傳輸層用于流量控制的一種有效措施针饥,也被稱為通告窗口⌒杵担滑動窗口是數(shù)據(jù)接收方設(shè)置的窗口大小,隨后接收方會把窗口大小告訴發(fā)送方筷凤,以此限制發(fā)送方每次發(fā)送數(shù)據(jù)的大小昭殉,從而達(dá)到流量控制的目的。這樣數(shù)據(jù)發(fā)送方不需要每發(fā)送一組數(shù)據(jù)就阻塞等待接收方確認(rèn)藐守,允許發(fā)送方同時發(fā)送多個數(shù)據(jù)分組挪丢,每次發(fā)送的數(shù)據(jù)都會被限制在窗口大小內(nèi)。由此可見卢厂,滑動窗口可以大幅度提升網(wǎng)絡(luò)吞吐量乾蓬。

現(xiàn)在來看一下滑動窗口是如何造成粘包、拆包的慎恒?

粘包:假設(shè)發(fā)送方的每256 bytes表示一個完整的報文任内,接收方由于數(shù)據(jù)處理不及時撵渡,這256個字節(jié)的數(shù)據(jù)都會被緩存到SO_RCVBUF(接收緩存區(qū))中。如果接收方的SO_RCVBUF中緩存了多個報文死嗦,那么對于接收方而言趋距,這就是粘包。

拆包:考慮另外一種情況越除,假設(shè)接收方的窗口只剩了128节腐,意味著發(fā)送方最多還可以發(fā)送128字節(jié),而由于發(fā)送方的數(shù)據(jù)大小是256字節(jié)摘盆,因此只能發(fā)送前128字節(jié)翼雀,等到接收方ack后,才能發(fā)送剩余字節(jié)孩擂。這就造成了拆包锅纺。

Nagle算法

TCP/IP協(xié)議中,無論發(fā)送多少數(shù)據(jù)肋殴,總是要在數(shù)據(jù)(DATA)前面加上協(xié)議頭(TCP Header+IP Header)囤锉,同時,對方接收到數(shù)據(jù)护锤,也需要發(fā)送ACK表示確認(rèn)官地。

即使從鍵盤輸入的一個字符,占用一個字節(jié)烙懦,可能在傳輸上造成41字節(jié)的包驱入,其中包括1字節(jié)的有用信息和40字節(jié)的首部數(shù)據(jù)。這種情況轉(zhuǎn)變成了4000%的消耗氯析,這樣的情況對于重負(fù)載的網(wǎng)絡(luò)來是無法接受的亏较。

為了盡可能的利用網(wǎng)絡(luò)帶寬,TCP總是希望盡可能的發(fā)送足夠大的數(shù)據(jù)掩缓。(一個連接會設(shè)置MSS參數(shù)雪情,因此,TCP/IP希望每次都能夠以MSS尺寸的數(shù)據(jù)塊來發(fā)送數(shù)據(jù))你辣。

Nagle算法就是為了盡可能發(fā)送大塊數(shù)據(jù)巡通,避免網(wǎng)絡(luò)中充斥著許多小數(shù)據(jù)塊。

Nagle算法的基本定義是任意時刻舍哄,最多只能有一個未被確認(rèn)的小段宴凉。 所謂“小段”,指的是小于MSS尺寸的數(shù)據(jù)塊表悬,所謂“未被確認(rèn)”弥锄,是指一個數(shù)據(jù)塊發(fā)送出去后,沒有收到對方發(fā)送的ACK確認(rèn)該數(shù)據(jù)已收到。

Nagle算法的規(guī)則:

  • 如果SO_SNDBUF(發(fā)送緩沖區(qū))中的數(shù)據(jù)長度達(dá)到MSS籽暇,則允許發(fā)送温治;
  • 如果該SO_SNDBUF中含有FIN,表示請求關(guān)閉連接图仓,則先將SO_SNDBUF中的剩余數(shù)據(jù)發(fā)送罐盔,再關(guān)閉;
  • 設(shè)置了TCP_NODELAY=true選項救崔,則允許發(fā)送惶看。TCP_NODELAY是取消TCP的確認(rèn)延遲機制,相當(dāng)于禁用了Nagle 算法六孵。
  • 未設(shè)置TCP_CORK選項時纬黎,若所有發(fā)出去的小數(shù)據(jù)包(包長度小于MSS)均被確認(rèn),則允許發(fā)送;
  • 上述條件都未滿足劫窒,但發(fā)生了超時(一般為200ms)本今,則立即發(fā)送。

TCP拆包/粘包問題的解決策略

由于底層的TCP無法理解上層的業(yè)務(wù)數(shù)據(jù)主巍,所以在底層是無法保證數(shù)據(jù)包不被拆分和重組的冠息,這個問題只能通過上層的應(yīng)用協(xié)議棧設(shè)計來解決,根據(jù)業(yè)界的主流協(xié)議的解決方案孕索,可以歸納如下:

  • 消息定長逛艰,例如每個報文的大小為固定長度200字節(jié),如果不夠搞旭,空位補空格散怖。假設(shè)我們需要發(fā)送的數(shù)據(jù)是| AB | CDEF | GHIJ | K | LM | 5條數(shù)據(jù),我們的固定長度為 4 字節(jié)肄渗,那么這5 條數(shù)據(jù)一共需要發(fā)送 4 個報文:
+------+------+------+------+

| ABCD | EFGH | IJKL | M000 |

+------+------+------+------+

消息定長法使用非常簡單镇眷,但是缺點也非常明顯,無法很好設(shè)定固定長度的值翎嫡,如果長度太大會造成字節(jié)浪費欠动,長度太小又會影響消息傳輸,所以在一般情況下消息定長法不會被采用钝的。

  • 在包尾增加回車換行符進(jìn)行分割翁垂,以下例子采用\n來分割
+-------------------------+

| AB\nCDEF\nGHIJ\nK\nLM\n |

+-------------------------+

由于在發(fā)送報文時尾部需要添加特定分隔符,所以對于分隔符的選擇一定要避免和消息體中字符相同硝桩,以免沖突。否則可能出現(xiàn)錯誤的消息拆分枚荣。比較推薦的做法是將消息進(jìn)行編碼碗脊,例如 base64 編碼,然后可以選擇 64 個編碼字符之外的字符作為特定分隔符。特定分隔符法在消息協(xié)議足夠簡單的場景下比較高效衙伶,例如大名鼎鼎的 Redis 在通信過程中采用的就是換行分隔符祈坠。

  • 將消息分為消息頭和消息體,消息頭中包含表示消息總長度(或消息體長度)的字段矢劲,通常設(shè)計思路為消息頭的第一個字段使用int32表示消息的總長度
消息頭     消息體

+--------+----------+

| Length |  Content |

+--------+----------+

+-----+-------+-------+----+-----+

| 2AB | 4CDEF | 4GHIJ | 1K | 2LM |

+-----+-------+-------+----+-----+

消息長度 + 消息內(nèi)容的使用方式非常靈活赦拘,且不會存在消息定長法和特定分隔符法的明顯缺陷。當(dāng)然在消息頭中不僅只限于存放消息的長度芬沉,而且可以自定義其他必要的擴展字段躺同,例如消息版本、算法類型等丸逸。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蹋艺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子黄刚,更是在濱河造成了極大的恐慌捎谨,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件憔维,死亡現(xiàn)場離奇詭異涛救,居然都是意外死亡,警方通過查閱死者的電腦和手機业扒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門检吆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凶赁,你說我怎么就攤上這事咧栗。” “怎么了虱肄?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵致板,是天一觀的道長。 經(jīng)常有香客問我咏窿,道長斟或,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任集嵌,我火速辦了婚禮萝挤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘根欧。我一直安慰自己怜珍,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布凤粗。 她就那樣靜靜地躺著酥泛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上柔袁,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天呆躲,我揣著相機與錄音,去河邊找鬼捶索。 笑死插掂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腥例。 我是一名探鬼主播辅甥,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼院崇!你這毒婦竟也來了肆氓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤底瓣,失蹤者是張志新(化名)和其女友劉穎谢揪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捐凭,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡拨扶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了茁肠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片患民。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖垦梆,靈堂內(nèi)的尸體忽然破棺而出匹颤,到底是詐尸還是另有隱情,我是刑警寧澤托猩,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布印蓖,位于F島的核電站,受9級特大地震影響京腥,放射性物質(zhì)發(fā)生泄漏赦肃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一公浪、第九天 我趴在偏房一處隱蔽的房頂上張望他宛。 院中可真熱鬧,春花似錦欠气、人聲如沸厅各。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽讯检。三九已至琐鲁,卻和暖如春卫旱,著一層夾襖步出監(jiān)牢的瞬間人灼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工顾翼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留投放,地道東北人。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓适贸,卻偏偏與公主長得像灸芳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拜姿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 熟悉tcp編程的可能都知道烙样,無論是服務(wù)器端還是客戶端,當(dāng)我們讀取或者發(fā)送數(shù)據(jù)的時候蕊肥,都需要考慮TCP底層的粘包/拆...
    彬榮閱讀 353評論 0 1
  • 一壁却、TCP 粘包和拆包基本介紹 TCP是面向連接的批狱,面向流的,提供高可靠性服務(wù)展东。收發(fā)兩端(客戶端和服務(wù)器端)都要有...
    小波同學(xué)閱讀 1,386評論 2 10
  • TCP底層的粘包/拆包機制 其實很多熟悉TCP編程的小伙伴們都知道赔硫,無論是客戶端還是服務(wù)端,當(dāng)我們讀取或者發(fā)送數(shù)據(jù)...
    櫻桃還是饅頭閱讀 314評論 0 0
  • 什么是粘包盐肃、拆包爪膊? 對于什么是粘包、拆包問題砸王,我想先舉兩個簡單的應(yīng)用場景: 客戶端和服務(wù)器建立一個連接推盛,客戶端發(fā)送...
    昨日已逝去閱讀 2,185評論 0 2
  • ????TCP是個“流”協(xié)議,所謂流,就是沒有界限的一串?dāng)?shù)據(jù).TCP底層并不了解上層業(yè)務(wù)數(shù)據(jù)的具體含義,它會根據(jù)T...
    劉澤田閱讀 226評論 0 1