TCP是個(gè)“流”協(xié)議,所謂流袖肥,就是沒(méi)有界限的一串?dāng)?shù)據(jù)咪辱。大家可以想象河里的流水,他們是連成一片的椎组,其間并沒(méi)有分界線油狂。TCP底層并不了解上層業(yè)務(wù)數(shù)據(jù)的具體含義,他會(huì)根據(jù)TCP緩沖區(qū)的實(shí)際情況進(jìn)行包的劃分,所以在業(yè)務(wù)上認(rèn)為专筷,一個(gè)完整的包可能會(huì)被TCP拆分成多個(gè)包進(jìn)行發(fā)送弱贼,也有可能把多個(gè)小的包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送。這就是TCP所謂的拆包和粘包的問(wèn)題磷蛹。
一吮旅、TCP粘包/拆包問(wèn)題說(shuō)明
我們可以通過(guò)圖解對(duì)TCP粘包和拆包問(wèn)題進(jìn)行說(shuō)明,粘包問(wèn)題如圖味咳。
假設(shè)客戶端分別發(fā)送了兩個(gè)數(shù)據(jù)包D1和D2給服務(wù)端庇勃,由于服務(wù)端一次讀取到的字節(jié)數(shù)是不確定的,故可能存在以下4中情況槽驶。
- 服務(wù)端分兩次讀取到了兩個(gè)獨(dú)立的數(shù)據(jù)包责嚷,分別是D1和D2,沒(méi)有粘包和拆包捺檬。
- 服務(wù)端一次接收到了兩個(gè)數(shù)據(jù)包再层,D1和D2粘在一起,被稱為TCP粘包
- 服務(wù)端分兩次讀取到了兩個(gè)數(shù)據(jù)包堡纬,第一次讀取到了完整的D1包和D2包的部分內(nèi)容聂受,第二次讀取到了D2包的剩余內(nèi)容,這被稱為TCP拆包烤镐。
- 服務(wù)端分兩次讀取到了兩個(gè)數(shù)據(jù)包蛋济,第一次讀取到了D1包的部分內(nèi)容D1_1,第二次讀取到了D1包的剩余內(nèi)容D1_2和D2包的整包炮叶。
如果此時(shí)服務(wù)端TCP接收滑窗非常小碗旅,而數(shù)據(jù)包D1和D2比較大,很有可能會(huì)發(fā)生第五種可能镜悉,即服務(wù)端分多次才能將D1和D2包接收完全祟辟,期間發(fā)生多次拆包。
二侣肄、TCP粘包/拆包發(fā)生的原因
問(wèn)題產(chǎn)生的原因有三個(gè)旧困,分別如下。
- 應(yīng)用程序write寫入的字節(jié)大小大于套接口發(fā)送緩沖區(qū)大小稼锅。
- 進(jìn)行MSS大小的TCP分段吼具。
- 以太網(wǎng)幀的payload大于MTU進(jìn)行IP分片。
三矩距、粘包問(wèn)題的解決策略
由于底層的TCP無(wú)法理解上層的業(yè)務(wù)數(shù)據(jù)拗盒,所以在底層是無(wú)法保證數(shù)據(jù)包不被拆分和重組的,這個(gè)問(wèn)題只能通過(guò)上層的應(yīng)用協(xié)議棧設(shè)計(jì)來(lái)解決锥债,根據(jù)業(yè)界的主流協(xié)議的解決方案陡蝇,可以歸納如下痊臭。
- 消息定長(zhǎng),例如每個(gè)報(bào)文的大小為固定長(zhǎng)度200字節(jié)毅整,如果不夠趣兄,空位補(bǔ)空格
- 在包尾增加回車換行符進(jìn)行分割,例如FTP協(xié)議
- 將消息分為消息頭和消息體悼嫉,消息頭中包含表示消息總長(zhǎng)度(或者消息體長(zhǎng)度)的字段,通常涉及思路為消息頭的第一個(gè)字段使用int32來(lái)表示消息的總長(zhǎng)度
- 更復(fù)雜的應(yīng)用層協(xié)議拼窥。