聲明:本文來自知乎,侵刪署尤。
一耙替、TCP粘包、拆包圖解
假設(shè)客戶端分別發(fā)送了兩個(gè)數(shù)據(jù)包D1和D2給服務(wù)端曹体,由于服務(wù)端一次讀取到字節(jié)數(shù)是不確定的俗扇,故可能存在以下四種情況:
服務(wù)端分兩次讀取到了兩個(gè)獨(dú)立的數(shù)據(jù)包,分別是D1和D2箕别,沒有粘包和拆包
服務(wù)端一次接受到了兩個(gè)數(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比較大护蝶,很有可能會(huì)發(fā)生第五種情況,即服務(wù)端分多次才能將D1和D2包完全接受翩迈,期間發(fā)生多次拆包持灰。
二、 粘包帽馋、拆包產(chǎn)生原因
產(chǎn)生原因主要有這3種:
滑動(dòng)窗口
MSS/MTU限制
Nagle算法
1搅方、滑動(dòng)窗口
TCP流量控制主要使用滑動(dòng)窗口協(xié)議,滑動(dòng)窗口是接受數(shù)據(jù)端使用的窗口大小绽族,用來告訴發(fā)送端接收端的緩存大小姨涡,以此可以控制發(fā)送端發(fā)送數(shù)據(jù)的大小,從而達(dá)到流量控制的目的吧慢。這個(gè)窗口大小就是我們一次傳輸幾個(gè)數(shù)據(jù)涛漂。對(duì)所有數(shù)據(jù)幀按順序賦予編號(hào),發(fā)送方在發(fā)送過程中始終保持著一個(gè)發(fā)送窗口检诗,只有落在發(fā)送窗口內(nèi)的幀才允許被發(fā)送匈仗;同時(shí)接收方也維持著一個(gè)接收窗口,只有落在接收窗口內(nèi)的幀才允許接收逢慌。這樣通過調(diào)整發(fā)送方窗口和接收方窗口的大小可以實(shí)現(xiàn)流量控制悠轩。
現(xiàn)在來看一下滑動(dòng)窗口是如何造成粘包、拆包的攻泼?
粘包:假設(shè)發(fā)送方的每256 bytes表示一個(gè)完整的報(bào)文火架,接收方由于數(shù)據(jù)處理不及時(shí),這256個(gè)字節(jié)的數(shù)據(jù)都會(huì)被緩存到SO_RCVBUF(接收緩存區(qū))中忙菠。如果接收方的SO_RCVBUF中緩存了多個(gè)報(bào)文何鸡,那么對(duì)于接收方而言,這就是粘包牛欢。
拆包:考慮另外一種情況骡男,假設(shè)接收方的窗口只剩了128,意味著發(fā)送方最多還可以發(fā)送128字節(jié)傍睹,而由于發(fā)送方的數(shù)據(jù)大小是256字節(jié)隔盛,因此只能發(fā)送前128字節(jié)犹菱,等到接收方ack后,才能發(fā)送剩余字節(jié)骚亿。這就造成了拆包已亥。
2熊赖、MSS和MTU分片
MSS:?是Maximum Segement Size縮寫来屠,表示TCP報(bào)文中data部分的最大長度,是TCP協(xié)議在OSI五層網(wǎng)絡(luò)模型中傳輸層對(duì)一次可以發(fā)送的最大數(shù)據(jù)的限制震鹉。
MTU:?最大傳輸單元是Maxitum Transmission Unit的簡寫俱笛,是OSI五層網(wǎng)絡(luò)模型中鏈路層(datalink layer)對(duì)一次可以發(fā)送的最大數(shù)據(jù)的限制。
當(dāng)需要傳輸?shù)臄?shù)據(jù)大于MSS或者M(jìn)TU時(shí)传趾,數(shù)據(jù)會(huì)被拆分成多個(gè)包進(jìn)行傳輸迎膜。由于MSS是根據(jù)MTU計(jì)算出來的,因此當(dāng)發(fā)送的數(shù)據(jù)滿足MSS時(shí)浆兰,必然滿足MTU磕仅。
為了更好的理解,我們先介紹一下在5層網(wǎng)絡(luò)模型中應(yīng)用通過TCP發(fā)送數(shù)據(jù)的流程:
對(duì)于應(yīng)用層來說簸呈,只關(guān)心發(fā)送的數(shù)據(jù)DATA榕订,將數(shù)據(jù)寫入socket在內(nèi)核中的發(fā)送緩沖區(qū)SO_SNDBUF即返回,操作系統(tǒng)會(huì)將SO_SNDBUF中的數(shù)據(jù)取出來進(jìn)行發(fā)送蜕便。傳輸層會(huì)在DATA前面加上TCP Header,構(gòu)成一個(gè)完整的TCP報(bào)文劫恒。
當(dāng)數(shù)據(jù)到達(dá)網(wǎng)絡(luò)層(network layer)時(shí),網(wǎng)絡(luò)層會(huì)在TCP報(bào)文的基礎(chǔ)上再添加一個(gè)IP Header轿腺,也就是將自己的網(wǎng)絡(luò)地址加入到報(bào)文中两嘴。到數(shù)據(jù)鏈路層時(shí),還會(huì)加上Datalink Header和CRC族壳。
當(dāng)?shù)竭_(dá)物理層時(shí)憔辫,會(huì)將SMAC(Source Machine,數(shù)據(jù)發(fā)送方的MAC地址)仿荆,DMAC(Destination Machine贰您,數(shù)據(jù)接受方的MAC地址 )和Type域加入。
可以發(fā)現(xiàn)數(shù)據(jù)在發(fā)送前赖歌,每一層都會(huì)在上一層的基礎(chǔ)上增加一些內(nèi)容枉圃,下圖演示了MSS、MTU在這個(gè)過程中的作用庐冯。
MTU是以太網(wǎng)傳輸數(shù)據(jù)方面的限制孽亲,每個(gè)以太網(wǎng)幀都有最小的大小64bytes最大不能超過1518bytes。刨去以太網(wǎng)幀的幀頭 (DMAC目的MAC地址48bit=6Bytes+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和幀尾 CRC校驗(yàn)部分4Bytes(這個(gè)部分有時(shí)候大家也把它叫做FCS)展父,那么剩下承載上層協(xié)議的地方也就是Data域最大就只能有1500Bytes這個(gè)值 我們就把它稱之為MTU返劲。
由于MTU限制了一次最多可以發(fā)送1500個(gè)字節(jié)玲昧,而TCP協(xié)議在發(fā)送DATA時(shí),還會(huì)加上額外的TCP Header和Ip Header篮绿,因此刨去這兩個(gè)部分孵延,就是TCP協(xié)議一次可以發(fā)送的實(shí)際應(yīng)用數(shù)據(jù)的最大大小,也就是MSS亲配。
MSS長度=MTU長度-IP Header-TCP Header
TCP Header的長度是20字節(jié)尘应,IPv4中IP Header長度是20字節(jié),IPV6中IP Header長度是40字節(jié)吼虎,因此:在IPV4中犬钢,以太網(wǎng)MSS可以達(dá)到1460byte;在IPV6中思灰,以太網(wǎng)MSS可以達(dá)到1440byte玷犹。
需要注意的是MSS表示的一次可以發(fā)送的DATA的最大長度,而不是DATA的真實(shí)長度洒疚。發(fā)送方發(fā)送數(shù)據(jù)時(shí)歹颓,當(dāng)SO_SNDBUF中的數(shù)據(jù)量大于MSS時(shí),操作系統(tǒng)會(huì)將數(shù)據(jù)進(jìn)行拆分油湖,使得每一部分都小于MSS巍扛,這就是拆包,然后每一部分都加上TCP Header肺魁,構(gòu)成多個(gè)完整的TCP報(bào)文進(jìn)行發(fā)送电湘,當(dāng)然經(jīng)過網(wǎng)絡(luò)層和數(shù)據(jù)鏈路層的時(shí)候,還會(huì)分別加上相應(yīng)的內(nèi)容鹅经。
需要注意:?默認(rèn)情況下寂呛,與外部通信的網(wǎng)卡的MTU大小是1500個(gè)字節(jié)。而本地回環(huán)地址的MTU大小為65535瘾晃,這是因?yàn)楸镜販y試時(shí)數(shù)據(jù)不需要走網(wǎng)卡贷痪,所以不受到1500的限制。
3蹦误、 Nagle算法
TCP/IP協(xié)議中劫拢,無論發(fā)送多少數(shù)據(jù),總是要在數(shù)據(jù)(DATA)前面加上協(xié)議頭(TCP Header+IP Header)强胰,同時(shí)舱沧,對(duì)方接收到數(shù)據(jù),也需要發(fā)送ACK表示確認(rèn)偶洋。
即使從鍵盤輸入的一個(gè)字符熟吏,占用一個(gè)字節(jié),可能在傳輸上造成41字節(jié)的包,其中包括1字節(jié)的有用信息和40字節(jié)的首部數(shù)據(jù)牵寺。這種情況轉(zhuǎn)變成了4000%的消耗悍引,這樣的情況對(duì)于重負(fù)載的網(wǎng)絡(luò)來是無法接受的。
為了盡可能的利用網(wǎng)絡(luò)帶寬帽氓,TCP總是希望盡可能的發(fā)送足夠大的數(shù)據(jù)趣斤。(一個(gè)連接會(huì)設(shè)置MSS參數(shù),因此黎休,TCP/IP希望每次都能夠以MSS尺寸的數(shù)據(jù)塊來發(fā)送數(shù)據(jù))浓领。
Nagle算法就是為了盡可能發(fā)送大塊數(shù)據(jù),避免網(wǎng)絡(luò)中充斥著許多小數(shù)據(jù)塊奋渔。
Nagle算法的基本定義是任意時(shí)刻镊逝,最多只能有一個(gè)未被確認(rèn)的小段。 所謂“小段”嫉鲸,指的是小于MSS尺寸的數(shù)據(jù)塊,所謂“未被確認(rèn)”歹啼,是指一個(gè)數(shù)據(jù)塊發(fā)送出去后玄渗,沒有收到對(duì)方發(fā)送的ACK確認(rèn)該數(shù)據(jù)已收到。
Nagle算法的規(guī)則:
如果SO_SNDBUF(發(fā)送緩沖區(qū))中的數(shù)據(jù)長度達(dá)到MSS狸眼,則允許發(fā)送藤树;
如果該SO_SNDBUF中含有FIN,表示請(qǐng)求關(guān)閉連接拓萌,則先將SO_SNDBUF中的剩余數(shù)據(jù)發(fā)送岁钓,再關(guān)閉;
設(shè)置了TCP_NODELAY=true選項(xiàng)微王,則允許發(fā)送屡限。TCP_NODELAY是取消TCP的確認(rèn)延遲機(jī)制,相當(dāng)于禁用了Nagle 算法炕倘。
未設(shè)置TCP_CORK選項(xiàng)時(shí)钧大,若所有發(fā)出去的小數(shù)據(jù)包(包長度小于MSS)均被確認(rèn),則允許發(fā)送;
上述條件都未滿足罩旋,但發(fā)生了超時(shí)(一般為200ms)啊央,則立即發(fā)送。