网络通信协议之粘包问题
TCP如何保證消息順序以及可靠性到達,以及TCP的流量控制,擁塞控制
2. TCP可靠性傳輸傳輸?shù)膶崿F(xiàn)
以字節(jié)為單位的滑動窗口
超時重傳時間的選擇
選擇確定SACK
面向連接:意味著兩個使用TCP的應(yīng)用(通常是一個客戶和一個服務(wù)器)在彼此交換數(shù)據(jù)之前必須先建立一個TCP連接
在一個TCP連接中,僅有兩方進行彼此通信,廣播和多播不能用于TCP。
3. TCP通過下列方式來提供可靠性:
應(yīng)用數(shù)據(jù)被分割成TCP認為最適合發(fā)送的數(shù)據(jù)塊。這和UDP完全不同,應(yīng)用程序產(chǎn)生的數(shù)據(jù)報長度將保持不變。 (將數(shù)據(jù)截斷為合理的長度)
當(dāng)TCP發(fā)出一個段后,它啟動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發(fā)這個報文段。 (超時重發(fā))
當(dāng)TCP收到發(fā)自TCP連接另一端的數(shù)據(jù),它將發(fā)送一個確認。這個確認不是立即發(fā)送,通常將推遲幾分之一秒 。 (對于收到的請求,給出確認響應(yīng)) (之所以推遲,可能是要對包做完整校驗)
TCP將保持它首部和數(shù)據(jù)的檢驗和。這是一個端到端的檢驗和,目的是檢測數(shù)據(jù)在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP將丟棄這個報文段和不確認收到此報文段。 (校驗出包有錯,丟棄報文段,不給出響應(yīng),TCP發(fā)送數(shù)據(jù)端,超時時會重發(fā)數(shù)據(jù))
既然TCP報文段作為IP數(shù)據(jù)報來傳輸,而IP數(shù)據(jù)報的到達可能會失序,因此TCP報文段的到達也可能會失序。如果必要,TCP將對收到的數(shù)據(jù)進行重新排序,將收到的數(shù)據(jù)以正確的順序交給應(yīng)用層。 (對失序數(shù)據(jù)進行重新排序,然后才交給應(yīng)用層)
既然IP數(shù)據(jù)報會發(fā)生重復(fù),TCP的接收端必須丟棄重復(fù)的數(shù)據(jù)。(對于重復(fù)數(shù)據(jù),能夠丟棄重復(fù)數(shù)據(jù))
TCP還能提供流量控制。TCP連接的每一方都有固定大小的緩沖空間。TCP的接收端只允許另一端發(fā)送接收端緩沖區(qū)所能接納的數(shù)據(jù)。這將防止較快主機致使較慢主機的緩沖區(qū)溢出。(TCP可以進行流量控制,防止較快主機致使較慢主機的緩沖區(qū)溢出)TCP使用的流量控制協(xié)議是可變大小的滑動窗口協(xié)議。
什么是字節(jié)流服務(wù):
兩個應(yīng)用程序通過TCP連接交換8bit字節(jié)構(gòu)成的字節(jié)流
TCP不在字節(jié)流中插入記錄標(biāo)識符。我們將這稱為字節(jié)流服務(wù)(bytestreamservice)。
TCP對字節(jié)流的內(nèi)容不作任何解釋。
TCP不知道傳輸?shù)臄?shù)據(jù)字節(jié)流是二進制數(shù)據(jù),還是ASCII字符、EBCDIC字符或者其他類型數(shù)據(jù)。
對字節(jié)流的解釋由TCP連接雙方的應(yīng)用層解釋。
4. TCP保證消息順序:
大家都知道,TCP提供了最可靠的數(shù)據(jù)傳輸,它給發(fā)送的每個數(shù)據(jù)包做順序化(這看起來非常煩瑣),然而,如果TCP沒有這樣煩瑣的操作,那么,可能會造成更多的麻煩。如造成數(shù)據(jù)包的重傳、順序的顛倒甚至造成數(shù)據(jù)包的丟失。那么,TCP具體是通過怎樣的方式來保證數(shù)據(jù)的順序化傳輸呢?
主機每次發(fā)送數(shù)據(jù)時,TCP就給每個數(shù)據(jù)包分配一個序列號并且在一個特定的時間內(nèi)等待接收主機對分配的這個序列號進行確認,如果發(fā)送主機在一個特定時間內(nèi)沒有收到接收主機的確認,則發(fā)送主機會重傳此數(shù)據(jù)包。接收主機利用序列號對接收的數(shù)據(jù)進行確認,以便檢測對方發(fā)送的數(shù)據(jù)是否有丟失或者亂序等,接收主機一旦收到已經(jīng)順序化的數(shù)據(jù),它就將這些數(shù)據(jù)按正確的順序重組成數(shù)據(jù)流并傳遞到高層進行處理。
具體步驟如下:
為了保證數(shù)據(jù)包的可靠傳遞,發(fā)送方必須把已發(fā)送的數(shù)據(jù)包保留在緩沖區(qū);
并為每個已發(fā)送的數(shù)據(jù)包啟動一個超時定時器;
如在定時器超時之前收到了對方發(fā)來的應(yīng)答信息(可能是對本包的應(yīng)答,也可以是對本包后續(xù)包的應(yīng)答),則釋放該數(shù)據(jù)包占用的緩沖區(qū);
否則,重傳該數(shù)據(jù)包,直到收到應(yīng)答或重傳次數(shù)超過規(guī)定的最大次數(shù)為止。
接收方收到數(shù)據(jù)包后,先進行CRC校驗,如果正確則把數(shù)據(jù)交給上層協(xié)議,然后給發(fā)送方發(fā)送一個累計應(yīng)答包,表明該數(shù)據(jù)已收到,如果接收方正好也有數(shù)據(jù)要發(fā)給發(fā)送方,應(yīng)答包也可方在數(shù)據(jù)包中捎帶過去。
粘包問題
盡管TCP能夠有效的保證每個字節(jié)序和包順序是按照順序接收的,但是仍然會存在粘包的問題,具體如下:
1. 粘包產(chǎn)生的原因
如果客戶端連續(xù)不斷的向服務(wù)端發(fā)送數(shù)據(jù)包時,服務(wù)端接收的數(shù)據(jù)會出現(xiàn)兩個數(shù)據(jù)包粘在一起的情況,這就是TCP協(xié)議中經(jīng)常會遇到的粘包以及拆包問題。
UDP是基于報文發(fā)送的,在UDP首部采用了16bit來指示UDP數(shù)據(jù)報文長度,因此在應(yīng)用層能很好的將不同的數(shù)據(jù)報文區(qū)分開,從而避免粘包和拆包問題。
原因有以下兩點:
TCP是基于字節(jié)流的,雖然應(yīng)用層和傳輸層之間的數(shù)據(jù)交互是大小不等的數(shù)據(jù)塊,但是TCP把這些數(shù)據(jù)塊僅僅看成一連串無結(jié)構(gòu)的字節(jié)流,沒有邊界;
在TCP的首部沒有表示數(shù)據(jù)長度的字段,基于上面兩點,在使用TCP傳輸數(shù)據(jù)時,才有粘包或者拆包現(xiàn)象發(fā)生的可能。
2、粘包/拆包的表現(xiàn)形式
現(xiàn)在假設(shè)客戶端向服務(wù)端連續(xù)發(fā)送了兩個數(shù)據(jù)包,用packet1和packet2來表示,那么服務(wù)端收到的數(shù)據(jù)可以分為三種,現(xiàn)列舉如下:
第一種情況,接收端正常收到兩個數(shù)據(jù)包,即沒有發(fā)生拆包和粘包的現(xiàn)象,此種情況不在本文的討論范圍內(nèi)。
第二種情況,接收端只收到一個數(shù)據(jù)包,由于TCP是不會出現(xiàn)丟包的,所以這一個數(shù)據(jù)包中包含了發(fā)送端發(fā)送的兩個數(shù)據(jù)包的信息,這種現(xiàn)象即為粘包。這種情況由于接收端不知道這兩個數(shù)據(jù)包的界限,所以對于接收端來說很難處理。
第三種情況,這種情況有兩種表現(xiàn)形式,如下圖。接收端收到了兩個數(shù)據(jù)包,但是這兩個數(shù)據(jù)包要么是不完整的,要么就是多出來一塊,這種情況即發(fā)生了拆包和粘包。這兩種情況如果不加特殊處理,對于接收端同樣是不好處理的。
3、粘包/拆包發(fā)生的原因
發(fā)生TCP粘包或拆包有很多原因,現(xiàn)列出常見的幾點:
1、要發(fā)送的數(shù)據(jù)大于TCP發(fā)送緩沖區(qū)剩余空間大小,將會發(fā)生拆包。
2、待發(fā)送數(shù)據(jù)大于MSS(最大報文長度),TCP在傳輸前將進行拆包。
3、要發(fā)送的數(shù)據(jù)小于TCP發(fā)送緩沖區(qū)的大小,TCP將多次寫入緩沖區(qū)的數(shù)據(jù)一次發(fā)送出去,將會發(fā)生粘包。
4、接收數(shù)據(jù)端的應(yīng)用層沒有及時讀取接收緩沖區(qū)中的數(shù)據(jù),將發(fā)生粘包。
4、粘包/拆包的解決辦法
通過以上分析,我們清楚了粘包或拆包發(fā)生的原因,那么如何解決這個問題呢?問題的關(guān)鍵在于如何給每個數(shù)據(jù)包添加邊界信息,常用的方法如下:
發(fā)送端給每個數(shù)據(jù)包添加包首部,首部中應(yīng)該至少包含數(shù)據(jù)包長度,這樣接收端在接收到數(shù)據(jù)后,通過讀取包首部長度字段便知道每一個數(shù)據(jù)包實際長度了。
發(fā)送端將每個數(shù)據(jù)包封裝為固定長度(不夠的可以通過補0填充),這樣接收端每次從接收緩沖區(qū)中讀取固定長度的數(shù)據(jù)就自然而然的把每個數(shù)據(jù)包拆分開來。
可以在數(shù)據(jù)包之間設(shè)置邊界,如添加特殊符號,這樣,接收端通過這個邊界就可以將不同的數(shù)據(jù)包拆分開。
如何設(shè)置合理的邊界特殊符號呢?
實際上,所謂的邊界符號也就是 Header(Magic Number) ,由于傳輸?shù)臄?shù)據(jù)可能出現(xiàn)各種各樣的情形,因此選用一種合適的 Header 字段來避免數(shù)據(jù)字段中出現(xiàn) Header 邊界符號,需要根據(jù)如下的步驟進行分析:
根據(jù)實際需求,確定待傳輸數(shù)據(jù)的取值范圍,例如:傳輸由12bit-ADC采集的電壓值,其取值范圍為 0x000 ~ 0xFFE,因此,直接取12bit來傳輸ADC數(shù)據(jù) (0x000 ~ 0x0FFF)
根據(jù)數(shù)據(jù)取值范圍,寫出最大數(shù)據(jù)組成的TCP數(shù)據(jù)包,例如:0xFFE|FFE|FFE| ~
根據(jù)數(shù)據(jù)包情況,選擇至少比原始數(shù)據(jù)多4bit的數(shù)據(jù)作為頭Header,假設(shè)為0xXXXX,將粘包數(shù)據(jù)包拆包為4Bytes數(shù)據(jù)可以得到三種情形(4Bytes滑動窗口):
FFEF:由于F位可能出現(xiàn)任意可能,因此此情形下的可能組合情況為 0xXXEX --> 第3位E處必須是F,才能夠避免出現(xiàn)此情形的重復(fù)
FEFF:由于F位可能出現(xiàn)任意可能,因此此情形下的可能組合情況為 0xXEXX --> 第2位E處必須是F,才能夠避免出現(xiàn)此情形的重復(fù)
EFFE:由于F位可能出現(xiàn)任意可能,因此此情形下的可能組合情況為 0xEXXE --> 第1位E處 或 第4位E處 任一處為F,即夠避免出現(xiàn)此情形的重復(fù)
根據(jù)上述分析結(jié)果可知,Header的取值應(yīng)該為 0xFFFX 或 0xXFFF 即可!
注: 其他類似情況分析如下(0x00000~0xFFFFE):
根據(jù)上圖分析,可用的Header為 \(0x(F_{1}/X_{6})(F_{2}/X_{7})(F_{3}/X_{8})F | F(F_{6}/X_{1})(F_{7}/X_{2})(F_{8}/X_{1})\)
結(jié)論: 對于最大值為0XFFF...E取值范圍 Nbits 的數(shù)據(jù)包,取Header為 N+4 bits,且 Header=0xFFFF+FE(最后的E為多出來的4Bit)
轉(zhuǎn)載請注明出處!感謝GISPALAB實驗室的老師和同學(xué)們的幫助和支持~
總結(jié)
以上是生活随笔為你收集整理的网络通信协议之粘包问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小程序代码压缩实践
- 下一篇: android表情加文字图片,Andro