天天看點

tcp粘包問題(經典分析)

為什麼會産生粘包問題呢???‘

1,

tcp粘包問題(經典分析)

從資料發送的過程中,經過那些步驟來看:

應用層首先要将自己的資料通過套接字發送,首先要調用一個write方法:(将應用程序緩沖區中的資料拷貝到套接口發送緩沖區SO_SNDBUF,有一個大小的限制),如果應用程序緩沖區的一條消息的位元組的大小超過了發送緩沖區的大小,就有可能産生粘包問題,因為消息已經被分割了,有可能一部分已經被發送出去了,對方已經接受了,但是另外一部分可能剛放入套接口發送緩沖區裡準備進一步發送,就直接導緻接受的後一部分,直接導緻了粘包問題的出現。

2,就是說:TCP是基于位元組流的,隻維護發送出去多少,确認了多少,并沒有維護消息與消息之間的邊界,因而極有可能導緻粘包問題,(應該在應用層維護一個消息邊界,加\n)

TCP所發送的的位元組流中存在一個MSS(最大封包端長度),如果所發送的消息的位元組過長,那麼會對所發送的消息進行分割,那麼也會直接導緻粘包

3,鍊路層所發送的資料有一個最大傳輸單元(MTU)的限制(以太網的MTU是1500bytes),如果我們所傳輸的資訊超過了限制,那麼會在IP層進行分組,或者分片,這也可能導緻消息的粘包問題的産生

      假設發送者的協定高層向IP層發送了長度為3008 bytes的資料封包,則該封包在添加20 bytes的IP標頭後IP包的總 長度是 3028 bytes,因為3028 > 1500,是以該資料封包将被分片,分片過程如下:

1. 首先計算最大的IP包中IP淨荷的長度 =MTU-IP標頭長度=1500-20= 1480 bytes。 2. 然後把3028 bytes按照1480 bytes的長度分片,将要分為3片,3028= 1480+1480+68。 3. 最後發送者将為3個分片分别添加IP標頭,組成3個IP包後再發送,3個IP包的長度分别為1500 bytes、1500 bytes和 88 bytes。

      網絡通訊中:我們要盡量避免發生分片和重組,因為分片重組對網絡性能影響較大,MTU大小的選擇有協定協商方式,通過全路徑的MTU發現機制,找到整條路徑的最小MTU(也就是路徑MTU),然後發送的封包段都小于等于路徑MTU,這也就簡單的避免了傳輸過程發生分片,提高轉發性能。需要注意的是:MTU協定發現機制并不能總是有效,這就需要根據網絡情況進行合理的選擇合理的MTU。是以分片也是不可避免的。但是我們能做的就是:讓分片盡量在終端進行,而不是在傳輸過程中分片。

4,當然,那個流量控制,擁塞控制也可能導緻粘包

5,還有可能tcp的延遲發送問題

是以:

即然,在傳輸層,鍊路層,網絡層沒有維護消息跟消息的邊界,那麼在應用層就要維持消息與消息的邊界

1,發送的消息是一個定長包的情況,那麼接收方就可以接受定長的位元組‘

2.可以在包的尾部加上一個:\r\n    (我們的ftp伺服器就是這樣實作的),但是這個東西整體上來說:還是不太好,      假如消息裡面本就含有\r\n的話,那麼就會有點問題了,當然還是可以加轉義字元的。

3,采用標頭加包體長度的方法,一般情況下,標頭是定長的,假如標頭是4個位元組,可以先接受標頭的4個位元組,進而    計算出包體的長度,然後繼續進行接受。

4,當然,我們還可以使用更複雜的應用層協定。

這兩天看csdn有一些關于socket粘包,socket緩沖區設定的問題,發現自己不是很清楚,是以查資料了解記錄一下: 

一 .兩個簡單概念長連接配接與短連接配接:

1.長連接配接

    Client方與Server方先建立通訊連接配接,連接配接建立後不斷開, 然後再進行封包發送和接收。

2.短連接配接

    Client方與Server每進行一次封包收發交易時才進行通訊連接配接,交易完畢後立即斷開連接配接。此種方式常用于一點對多點

通訊,比如多個Client連接配接一個Server.

二、TCP協定簡介   

TCP是一個面向連接配接的傳輸層協定,雖然TCP不屬于ISO制定的協定集,但由于其在商業界和工業界的成功應用,它已成為事實上的網絡标準,廣泛應用于各種網絡主機間的通信。   

作為一個面向連接配接的傳輸層協定,TCP的目标是為使用者提供可靠的端到端連接配接,保證資訊有序無誤的傳輸。它除了提供基本的資料傳輸功能外,還為保證可靠性采用了資料編号、校驗和計算、資料确認等一系列措施。它對傳送的每個資料位元組都進行編号,并請求接收方回傳确認資訊(ACK)。發送方如果在規定的時間内沒有收到資料确認,就重傳該資料。資料編号使接收方能夠處理資料的失序和重複問題。資料誤碼問題通過在每個傳輸的資料段中增加校驗和予以解決,接收方在接收到資料後檢查校驗和,若校驗和有誤,則丢棄該有誤碼的資料段,并要求發送方重傳。流量控制也是保證可靠性的一個重要措施,若無流控,可能會因接收緩沖區溢出而丢失大量資料,導緻許多重傳,造成網絡擁塞惡性循環。TCP采用可變視窗進行流量控制,由接收方控制發送方發送的資料量。   

TCP為使用者提供了高可靠性的網絡傳輸服務,但可靠性保障措施也影響了傳輸效率。是以,在實際工程應用中,隻有關鍵資料的傳輸才采用TCP,而普通資料的傳輸一般采用高效率的UDP。   

三、粘包問題分析

TCP粘包是指發送方發送的若幹包資料到接收方接收時粘成一包,從接收緩沖區看,後一包資料的頭緊接着前一包資料的尾。   

出現粘包現象的原因是多方面的,它既可能由發送方造成,也可能由接收方造成。發送方引起的粘包是由TCP協定本身造成的,TCP為提高傳輸效率,發送方往往要收集到足夠多的資料後才發送一包資料。若連續幾次發送的資料都很少,通常TCP會根據優化算法把這些資料合成一包後一次發送出去,這樣接收方就收到了粘包資料。接收方引起的粘包是由于接收方使用者程序不及時接收資料,進而導緻粘包現象。這是因為接收方先把收到的資料放在系統接收緩沖區,使用者程序從該緩沖區取資料,若下一包資料到達時前一包資料尚未被使用者程序取走,則下一包資料放到系統接收緩沖區時就接到前一包資料之後,而使用者程序根據預先設定的緩沖區大小從系統接收緩沖區取資料,這樣就一次取到了多包資料(圖1所示)。   

粘包情況有兩種,一種是粘在一起的包都是完整的資料包(圖1、圖2所示),另一種情況是粘在一起的包有不完整的包(圖3所示),此處假設使用者接收緩沖區長度為m個位元組。   

不是所有的粘包現象都需要處理,若傳輸的資料為不帶結構的連續流資料(如檔案傳輸),則不必把粘連的包分開(簡稱分包)。但在實際工程應用中,傳輸的資料一般為帶結構的資料,這時就需要做分包處理。   

在處理定長結構資料的粘包問題時,分包算法比較簡單;在處理不定長結構資料的粘包問題時,分包算法就比較複雜。特别是如圖3所示的粘包情況,由于一包資料内容被分在了兩個連續的接收包中,處理起來難度較大。實際工程應用中應盡量避免出現粘包現象。   

二 .什麼時候需要考慮粘包問題?

1:如果利用tcp每次發送資料,就與對方建立連接配接,然後雙方發送完一段資料後,就關閉連接配接,這樣就不會出現粘包問題(因為隻有一種包結構,類似于http協定)。關閉連接配接主要要雙方都發送close連接配接(參考tcp關閉協定)。如:A需要發送一段字元串給B,那麼A與B建立連接配接,然後發送雙方都預設好的協定字元如"hello give me sth abour yourself",然後B收到封包後,就将緩沖區資料接收,然後關閉連接配接,這樣粘包問題不用考慮到,因為大家都知道是發送一段字元。

2:如果發送資料無結構,如檔案傳輸,這樣發送方隻管發送,接收方隻管接收存儲就ok,也不用考慮粘包

3:如果雙方建立連接配接,需要在連接配接後一段時間内發送不同結構資料,如連接配接後,有好幾種結構:

 1)"hello give me sth abour yourself"

 2)"Don't give me sth abour yourself"

   那這樣的話,如果發送方連續發送這個兩個包出去,接收方一次接收可能會是"hello give me sth abour yourselfDon't give me sth abour yourself" 這樣接收方就傻了,到底是要幹嘛?不知道,因為協定沒有規定這麼詭異的字元串,是以要處理把它分包,怎麼分也需要雙方組織一個比較好的包結構,是以一般可能會在頭加一個資料長度之類的包,以確定接收。

三 .粘包出現原因:在流傳輸中出現,UDP不會出現粘包,因為它有消息邊界(參考Windows 網絡程式設計)

1 發送端需要等緩沖區滿才發送出去,造成粘包

2 接收方不及時接收緩沖區的包,造成多個包接收

解決辦法:

為了避免粘包現象,可采取以下幾種措施。一是對于發送方引起的粘包現象,使用者可通過程式設計設定來避免,TCP提供了強制資料立即傳送的操作指令push,TCP軟體收到該操作指令後,就立即将本段資料發送出去,而不必等待發送緩沖區滿;二是對于接收方引起的粘包,則可通過優化程式設計、精簡接收程序工作量、提高接收程序優先級等措施,使其及時接收資料,進而盡量避免出現粘包現象;三是由接收方控制,将一包資料按結構字段,人為控制分多次接收,然後合并,通過這種手段來避免粘包。

以上提到的三種措施,都有其不足之處。第一種程式設計設定方法雖然可以避免發送方引起的粘包,但它關閉了優化算法,降低了網絡發送效率,影響應用程式的性能,一般不建議使用。第二種方法隻能減少出現粘包的可能性,但并不能完全避免粘包,當發送頻率較高時,或由于網絡突發可能使某個時間段資料包到達接收方較快,接收方還是有可能來不及接收,進而導緻粘包。第三種方法雖然避免了粘包,但應用程式的效率較低,對實時應用的場合不适合。

載自:http://blog.csdn.net/binghuazh/archive/2009/05/28/4222516.aspx

====================================================================

網絡通訊的封包和拆包 收藏

對于基于TCP開發的通訊程式,有個很重要的問題需要解決,就是封包和拆包.

一.為什麼基于TCP的通訊程式需要進行封包和拆包.

TCP是個"流"協定,所謂流,就是沒有界限的一串資料.大家可以想想河裡的流水,是連成一片的,其間是沒有分界線的.但一般通訊程式開發是需要定義一個個互相獨立的資料包的,比如用于登陸的資料包,用于登出的資料包.由于TCP"流"的特性以及網絡狀況,在進行資料傳輸時會出現以下幾種情況.

假設我們連續調用兩次send分别發送兩段資料data1和data2,在接收端有以下幾種接收情況(當然不止這幾種情況,這裡隻列出了有代表性的情況).

A.先接收到data1,然後接收到data2.

B.先接收到data1的部分資料,然後接收到data1餘下的部分以及data2的全部.

C.先接收到了data1的全部資料和data2的部分資料,然後接收到了data2的餘下的資料.

D.一次性接收到了data1和data2的全部資料.

對于A這種情況正是我們需要的,不再做讨論.對于B,C,D的情況就是大家經常說的"粘包",就需要我們把接收到的資料進行拆包,拆成一個個獨立的資料包.為了拆包就必須在發送端進行封包.

另:對于UDP來說就不存在拆包的問題,因為UDP是個"資料包"協定,也就是兩段資料間是有界限的,在接收端要麼接收不到資料要麼就是接收一個完整的一段資料,不會少接收也不會多接收.

二.為什麼會出現B.C.D的情況.

"粘包"可發生在發送端也可發生在接收端.

1.由Nagle算法造成的發送端的粘包:Nagle算法是一種改善網絡傳輸效率的算法.簡單的說,當我們送出一段資料給TCP發送時,TCP并不立刻發送此段資料,而是等待一小段時間,看看在等待期間是否還有要發送的資料,若有則會一次把這兩段資料發送出去.這是對Nagle算法一個簡單的解釋,詳細的請看相關書籍.象C和D的情況就有可能是Nagle算法造成的.

2.接收端接收不及時造成的接收端粘包:TCP會把接收到的資料存在自己的緩沖區中,然後通知應用層取資料.當應用層由于某些原因不能及時的把TCP的資料取出來,就會造成TCP緩沖區中存放了幾段資料.

三.怎樣封包和拆包.

   最初遇到"粘包"的問題時,我是通過在兩次send之間調用sleep來休眠一小段時間來解決.這個解決方法的缺點是顯而易見的,使傳輸效率大大降低,而且也并不可靠.後來就是通過應答的方式來解決,盡管在大多數時候是可行的,但是不能解決象B的那種情況,而且采用應答方式增加了通訊量,加重了網絡負荷. 再後來就是對資料包進行封包和拆包的操作.

    封包:

封包就是給一段資料加上標頭,這樣一來資料包就分為標頭和包體兩部分内容了(以後講過濾非法包時封包會加入"包尾"内容).標頭其實上是個大小固定的結構體,其中有個結構體成員變量表示包體的長度,這是個很重要的變量,其他的結構體成員可根據需要自己定義.根據標頭長度固定以及標頭中含有包體長度的變量就能正确的拆分出一個完整的資料包.

    對于拆包目前我最常用的是以下兩種方式.

    1.動态緩沖區暫存方式.之是以說緩沖區是動态的是因為當需要緩沖的資料長度超出緩沖區的長度時會增大緩沖區長度.

    大概過程描述如下:

    A,為每一個連接配接動态配置設定一個緩沖區,同時把此緩沖區和SOCKET關聯,常用的是通過結構體關聯.

    B,當接收到資料時首先把此段資料存放在緩沖區中.

    C,判斷緩存區中的資料長度是否夠一個標頭的長度,如不夠,則不進行拆包操作.

    D,根據標頭資料解析出裡面代表包體長度的變量.

    E,判斷緩存區中除標頭外的資料長度是否夠一個包體的長度,如不夠,則不進行拆包操作.

    F,取出整個資料包.這裡的"取"的意思是不光從緩沖區中拷貝出資料包,而且要把此資料包從緩存區中删除掉.删除的辦法就是把此包後面的資料移動到緩沖區的起始位址.

這種方法有兩個缺點.1.為每個連接配接動态配置設定一個緩沖區增大了記憶體的使用.2.有三個地方需要拷貝資料,一個地方是把資料存放在緩沖區,一個地方是把完整的資料包從緩沖區取出來,一個地方是把資料包從緩沖區中删除.第二種拆包的方法會解決和完善這些缺點.

前面提到過這種方法的缺點.下面給出一個改進辦法, 即采用環形緩沖.但是這種改進方法還是不能解決第一個缺點以及第一個資料拷貝,隻能解決第三個地方的資料拷貝(這個地方是拷貝資料最多的地方).第2種拆包方式會解決這兩個問題.

環形緩沖實作方案是定義兩個指針,分别指向有效資料的頭和尾.在存放資料和删除資料時隻是進行頭尾指針的移動.

2.利用底層的緩沖區來進行拆包

由于TCP也維護了一個緩沖區,是以我們完全可以利用TCP的緩沖區來緩存我們的資料,這樣一來就不需要為每一個連接配接配置設定一個緩沖區了.另一方面我們知道recv或者wsarecv都有一個參數,用來表示我們要接收多長長度的資料.利用這兩個條件我們就可以對第一種方法進行優化.

     對于阻塞SOCKET來說,我們可以利用一個循環來接收標頭長度的資料,然後解析出代表包體長度的那個變量,再用一個循環來接收包體長度的資料.

相關代碼如下:

char PackageHead[1024];

char PackageContext[1024*20];

int len;

PACKAGE_HEAD *pPackageHead;

while( m_bClose == false )

{

memset(PackageHead,0,sizeof(PACKAGE_HEAD));

len = m_TcpSock.ReceiveSize((char*)PackageHead,sizeof(PACKAGE_HEAD));

if( len == SOCKET_ERROR )

{

    break;

}

if(len == 0)

{

    break;

}

pPackageHead = (PACKAGE_HEAD *)PackageHead;

memset(PackageContext,0,sizeof(PackageContext));

if(pPackageHead->nDataLen>0)

{

len = m_TcpSock.ReceiveSize((char*)PackageContext,pPackageHead->nDataLen);

}

        }

m_TcpSock是一個封裝了SOCKET的類的變量,其中的ReceiveSize用于接收一定長度的資料,直到接收了一定長度的資料或者網絡出錯才傳回.

int winSocket::ReceiveSize( char* strData, int iLen )

{

if( strData == NULL )

return ERR_BADPARAM;

char *p = strData;

int len = iLen;

int ret = 0;

int returnlen = 0;

while( len > 0)

{

ret = recv( m_hSocket, p+(iLen-len), iLen-returnlen, 0 );

if ( ret == SOCKET_ERROR || ret == 0 )

{

return ret;

}

len -= ret;

returnlen += ret;

}

return returnlen;

}

對于非阻塞的SOCKET,比如完成端口,我們可以送出接收標頭長度的資料的請求,當 GetQueuedCompletionStatus傳回時,我們判斷接收的資料長度是否等于標頭長度,若等于,則送出接收包體長度的資料的請求,若不等于則送出接收剩餘資料的請求.當接收包體時,采用類似的方法.

載自: http://blog.csdn.net/fjcailei/archive/2009/06/17/4276463.aspx

======================================================================

幾個問題:http://www.qqgb.com/Program/VC/VCJQ/Program_200509.html

這個問題産生于程式設計中遇到的幾個問題:

1、使用TCP的Socket發送資料的時候,會出現發送出錯,WSAEWOULDBLOCK,在TCP中不是會保證發送的資料能夠安全的到達接收端的嗎?也有視窗機制去防止發送速度過快,為什麼還會出錯呢?

2、TCP協定,在使用Socket發送資料的時候,每次發送一個包,接收端是完整的接受到一個包還是怎麼樣?如果是每發一個包,就接受一個包,為什麼還會出現粘包問題,具體是怎麼運作的?

3、關于Send,是不是隻有在非阻塞狀态下才會出現實際發送的比指定發送的小?在阻塞狀态下會不會出現實際發送的比指定發送的小,就是說隻能出現要麼全發送,要麼不發送?在非阻塞狀态下,如果之發送了一些資料,要怎麼處理,調用了Send函數後,發現傳回值比指定的要小,具體要怎麼做?

4、最後一個問題,就是TCP/IP協定和Socket是什麼關系?是指具體的實作上,Socket是TCP/IP的實作?那麼為什麼會出現使用TCP協定的Socket會發送出錯(又回到第一個問題了,汗一個)

實在是有點暈了,如果我的問題有不清楚的地方,或者分數有問題,歡迎指出,謝謝

這個問題第1個回答:

1 應該是你的緩沖區不夠大,

2 tcp是流,沒有界限.也就所所謂的包.

3 阻塞也會出現這種現象,出現後繼續發送沒發送出去的.

4 tcp是協定,socket是一種接口,沒必然聯系.錯誤取決于你使用接口的問題,跟tcp沒關系.

這個問題第2個回答:

1 應該是你的緩沖區不夠大,

2 tcp是流,沒有界限.也就無所謂包.

3 阻塞也會出現這種現象,出現後繼續發送沒發送出去的.

4 tcp是協定,socket是一種接口,沒必然聯系.錯誤取決于你使用接口的問題,跟tcp沒關系.

這個問題第3個回答:

1、應該不是緩沖區大小問題,我試過設定緩沖區大小,不過這裡有個問題,就是就算我把緩沖區設定成幾G,也傳回成功,不過實際上怎麼可能設定那麼大、、、

3、出現沒發送完的時候要手動發送吧,有沒有具體的代碼實作?

4、當選擇TCP的Socket發送資料的時候,TCP中的視窗機制不是能防止發送速度過快的嗎?為什麼Socket在出現了WSAEWOULDBLOCK後沒有處理?

這個問題第4個回答:

1.在使用非阻塞模式的情況下,如果系統發送緩沖區已滿,并示及時發送到對端,就會産生該錯誤,繼續重試即可。

3.如果沒有發完就繼續發送後續部分即可。

這個問題第5個回答:

1、使用非阻塞模式時,如果目前操作不能立即完成則會傳回失敗,錯誤碼是WSAEWOULDBLOCK,這是正常的,程式可以先執行其它任務,過一段時間後再重試該操作。

2、發送與接收不是一一對應的,TCP會把各次發送的資料重新組合,可能合并也可能拆分,但發送次序是不變的。

3、在各種情況下都要根據send的傳回值來确定發送了多少資料,沒有發送完就再接着發。

4、socket是Windows提供網絡程式設計接口,TCP/IP是網絡傳輸協定,使用socket是可以使用多種協定,其中包括TCP/IP。

這個問題第6個回答:

up

這個問題第7個回答:

發送的過程是:發送到緩沖區和從緩沖區發送到網絡上

WSAEWOULDBLOCK和粘包都是出現在發送到緩沖區這個過程的

繼續閱讀