TCP粘包問題分析和解決(全)

TCP通訊粘包問題分析和解決(全)html

在socket網絡程序中,TCP和UDP分別是面向鏈接和非面向鏈接的。所以TCP的socket編程,收發兩端(客戶端和服務器端)都要有成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小、數據量小的數據,合併成一個大的數據塊,而後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。算法

對於UDP,不會使用塊的合併優化算法,這樣,實際上目前認爲,是因爲UDP支持的是一對多的模式,因此接收端的skbuff(套接字緩衝區)採用了鏈式結構來記錄每個到達的UDP包,在每一個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對於接收端來講,就容易進行區分處理了。因此UDP不會出現粘包問題。編程

====================================================================緩存

在介紹TCP以前先普及下兩個相關的概念,長鏈接和短鏈接。安全

1.長鏈接服務器

Client方與Server方先創建通信鏈接,鏈接創建後 不斷開, 而後再進行報文發送和接收。網絡

2.短鏈接異步

Client方與Server每進行一次報文收發交易時才進行通信鏈接,交易完畢後當即斷開鏈接。此種方式經常使用於一點對多點通信,好比多個Client鏈接一個Server.socket

 

 

TCP協議簡介tcp

TCP是一個面向鏈接的傳輸層協議,雖然TCP不屬於ISO制定的協議集,但因爲其在商業界和工業界的成功應用,它已成爲事實上的網絡標準,普遍應用於各類網絡主機間的通訊。

做爲一個面向鏈接的傳輸層協議,TCP的目標是爲用戶提供可靠的端到端鏈接,保證信息有序無誤的傳輸。它除了提供基本的數據傳輸功能外,還爲保證可靠性採用了數據編號、校驗和計算、數據確認等一系列措施。它對傳送的每一個數據字節都進行編號,並請求接收方回傳確認信息(ACK)。發送方若是在規定的時間內沒有收到數據確認,就重傳該數據。

(1)     數據編號使接收方可以處理數據的失序和重複問題。

(2)     數據誤碼問題經過在每一個傳輸的數據段中增長校驗和予以解決,接收方在接收到數據後檢查校驗和,若校驗和有誤,則丟棄該有誤碼的數據段,並要求發送方重傳。

(3)     流量控制也是保證可靠性的一個重要措施,若無流控,可能會因接收緩衝區溢出而丟失大量數據,致使許多重傳,形成網絡擁塞惡性循環。

(4)     TCP採用可變窗口進行流量控制,由接收方控制發送方發送的數據量。

TCP爲用戶提供了高可靠性的網絡傳輸服務,但可靠性保障措施也影響了傳輸效率。所以,在實際工程應用中,只有關鍵數據的傳輸才採用TCP,而普通數據的傳輸通常採用高效率的UDP。

 

保護消息邊界和流

那麼什麼是保護消息邊界和流呢?

保護消息邊界,就是指傳輸協議把數據看成一條獨立的消息在網上傳輸,接收端只能接收獨立的消息。也就是說存在保護消息邊界,接收端一次只能接收發送端發出的一個數據包。而面向流則是指無保護消息保護邊界的,若是發送端連續發送數據,接收端有可能在一次接收動做中,會接收兩個或者更多的數據包。

例如,咱們連續發送三個數據包,大小分別是2k,4k ,8k,這三個數據包,都已經到達了接收端的網絡堆棧中,若是使用UDP協議,無論咱們使用多大的接收緩衝區去接收數據,咱們必須有三次接收動做,纔可以把全部的數據包接收完.而使用TCP協議,咱們只要把接收的緩衝區大小設置在14k以上,咱們就可以一次把全部的數據包接收下來,只須要有一次接收動做。

 

注意:

這就是由於UDP協議的保護消息邊界使得每個消息都是獨立的。而流傳輸卻把數據看成一串數據流,他不認爲數據是一個一個的消息。因此有不少人在使用tcp協議通信的時候,並不清楚tcp是基於流的傳輸,當連續發送數據的時候,他們時常會認識tcp會丟包。其實否則,由於當他們使用的緩衝區足夠大時,他們有可能會一次接收到兩個甚至更多的數據包,而不少人每每會忽視這一點,只解析檢查了第一個數據包,而已經接收的其餘數據包卻被忽略了。因此你們若是要做這類的網絡編程的時候,必需要注意這一點。

 

結論:

(1)TCP爲了保證可靠傳輸,儘可能減小額外開銷(每次發包都要驗證),所以採用了流式傳輸,面向流的傳輸,相對於面向消息的傳輸,能夠減小發送包的數量,從而減小了額外開銷。可是,對於數據傳輸頻繁的程序來說,使用TCP可能會容易粘包。固然,對接收端的程序來說,若是機器負荷很重,也會在接收緩衝裏粘包。這樣,就須要接收端額外拆包,增長了工做量。所以,這個特別適合的是數據要求可靠傳輸,可是不須要太頻繁傳輸的場合(兩次操做間隔100ms,具體是由TCP等待發送間隔決定的,取決於內核中的socket的寫法)

(2)UDP,因爲面向的是消息傳輸,它把全部接收到的消息都掛接到緩衝區的接受隊列中,所以,它對於數據的提取分離就更加方便,可是,它沒有粘包機制,所以,當發送數據量較小的時候,就會發生數據包有效載荷較小的狀況,也會增長屢次發送的系統發送開銷(系統調用,寫硬件等)和接收開銷。所以,應該最好設置一個比較合適的數據包的包長,來進行UDP數據的發送。(UDP最大載荷爲1472,所以最好能每次傳輸接近這個數的數據量,這特別適合於視頻,音頻等大塊數據的發送,同時,經過減小握手來保證流媒體的實時性

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

粘包問題分析與對策

TCP粘包是指發送方發送的若干包數據到接收方接收時粘成一包,從接收緩衝區看,後一包數據的頭緊接着前一包數據的尾。

出現粘包現象的緣由是多方面的,它既可能由發送方形成,也可能由接收方形成。

 

何時須要考慮粘包問題

1若是利用tcp每次發送數據,就與對方創建鏈接,而後雙方發送完一段數據後,就關閉鏈接,這樣就不會出現粘包問題(由於只有一種包結構,相似於http協議)。

關閉鏈接主要是要雙方都發送close鏈接(參考tcp關閉協議)。如:A須要發送一段字符串給B,那麼A與B創建鏈接,而後發送雙方都默認好的協議字符如"hello give me sth abour yourself",而後B收到報文後,就將緩衝區數據接收,而後關閉鏈接,這樣粘包問題不用考慮到,由於你們都知道是發送一段字符。

2若是發送數據無結構,如文件傳輸,這樣發送方只管發送,接收方只管接收存儲就ok,也不用考慮粘包3若是雙方創建鏈接,須要在鏈接後一段時間內發送不一樣結構數據,如鏈接後,有好幾種結構:

1)"hellogive me sth abour yourself"

2)"Don'tgive me sth abour yourself"

那這樣的話,若是發送方連續發送這個兩個包出去,接收方一次接收可能會是"hellogive me sth abour yourselfDon't give me sth abour yourself"這樣接收方就傻了,究竟是要幹嗎?不知道,由於協議沒有規定這麼詭異的字符串,因此要處理把它分包,怎麼分也須要雙方組織一個比較好的包結構,因此通常可能會在頭加一個數據長度之類的包,以確保接收。

 

粘包出現緣由

簡單得說,在流傳輸中出現,UDP不會出現粘包,由於它有消息邊界(參考Windows網絡編程)

1發送端須要等緩衝區滿才發送出去,形成粘包

2接收方不及時接收緩衝區的包,形成多個包接收

具體點:

(1)發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一包數據。若連續幾回發送的數據都不多,一般TCP會根據優化算法把這些數據合成一包後一次發送出去,這樣接收方就收到了粘包數據。

(2)接收方引發的粘包是因爲接收方用戶進程不及時接收數據,從而致使粘包現象。這是由於接收方先把收到的數據放在系統接收緩衝區,用戶進程從該緩衝區取數據,若下一包數據到達時前一包數據還沒有被用戶進程取走,則下一包數據放到系統接收緩衝區時就接到前一包數據以後,而用戶進程根據預先設定的緩衝區大小從系統接收緩衝區取數據,這樣就一次取到了多包數據。

粘包狀況有兩種,一種是粘在一塊兒的包都是完整的數據包,另外一種狀況是粘在一塊兒的包有不完整的包。

不是全部的粘包現象都須要處理,若傳輸的數據爲不帶結構的連續流數據(如文件傳輸),則沒必要把粘連的包分開(簡稱分包)。但在實際工程應用中,傳輸的數據通常爲帶結構的數據,這時就須要作分包處理。

在處理定長結構數據的粘包問題時,分包算法比較簡單;在處理不定長結構數據的粘包問題時,分包算法就比較複雜。特別是粘在一塊兒的包有不完整的包的粘包狀況,因爲一包數據內容被分在了兩個連續的接收包中,處理起來難度較大。實際工程應用中應儘可能避免出現粘包現象。

 

爲了不粘包現象,可採起如下幾種措施:

(1)對於發送方引發的粘包現象,用戶可經過編程設置來避免,TCP提供了強制數據當即傳送的操做指令push,TCP軟件收到該操做指令後,就當即將本段數據發送出去,而沒必要等待發送緩衝區滿;

(2)對於接收方引發的粘包,則可經過優化程序設計、精簡接收進程工做量、提升接收進程優先級等措施,使其及時接收數據,從而儘可能避免出現粘包現象;

(3)由接收方控制,將一包數據按結構字段,人爲控制分屢次接收,而後合併,經過這種手段來避免粘包。

 

以上提到的三種措施,都有其不足之處。

(1)第一種編程設置方法雖然能夠避免發送方引發的粘包,但它關閉了優化算法,下降了網絡發送效率,影響應用程序的性能,通常不建議使用。

(2)第二種方法只能減小出現粘包的可能性,但並不能徹底避免粘包,當發送頻率較高時,或因爲網絡突發可能使某個時間段數據包到達接收方較快,接收方仍是有可能來不及接收,從而致使粘包。

(3)第三種方法雖然避免了粘包,但應用程序的效率較低,對實時應用的場合不適合。

 

一種比較周全的對策是:接收方建立一預處理線程,對接收到的數據包進行預處理,將粘連的包分開。對這種方法咱們進行了實驗,證實是高效可行的。

具體能夠參考:http://blog.csdn.net/soli/article/details/1297109

 

TCP無保護消息邊界的解決

針對這個問題,通常有3種解決方案:

(1)發送固定長度的消息

(2)把消息的尺寸與消息一塊發送

(3)使用特殊標記來區分消息間隔

其解決方法具體解決能夠參考:http://blog.csdn.net/zhangxinrun/article/details/6721427

 

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

網絡通信的封包和拆包

對於基於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來講,咱們能夠利用一個循環來接收包頭長度的數據,而後解析出表明包體長度的那個變量,再用一個循環來接收包體長度的數據。

編程實現見:http://blog.csdn.net/zhangxinrun/article/details/6721495


這個問題產生於編程中遇到的幾個問題:

一、使用TCP的Socket發送數據的時候,會出現發送出錯,WSAEWOULDBLOCK,在TCP中不是會保證發送的數據可以安全的到達接收端的嗎?也有窗口機制去防止發送速度過快,爲何還會出錯呢?

二、TCP協議,在使用Socket發送數據的時候,每次發送一個包,接收端是完整的接受到一個包仍是怎麼樣?若是是每發一個包,就接受一個包,爲何還會出現粘包問題,具體是怎麼運行的?

三、關於Send,是否是隻有在非阻塞狀態下才會出現實際發送的比指定發送的小?在阻塞狀態下會不會出現實際發送的比指定發送的小,就是說只能出現要麼全發送,要麼不發送?在非阻塞狀態下,若是之發送了一些數據,要怎麼處理,調用了Send函數後,發現返回值比指定的要小,具體要怎麼作?

四、最後一個問題,就是TCP/IP協議和Socket是什麼關係?是指具體的實現上,Socket是TCP/IP的實現?那麼爲何會出現使用TCP協議的Socket會發送出錯。


這個問題第1個回答:

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

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

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

4tcp是協議,socket是一種接口,沒必然聯繫.錯誤取決於你使用接口的問題,跟tcp不要緊.


這個問題第2個回答:

一、應該不是緩衝區大小問題,我試過設置緩衝區大小,不過這裏有個問題,就是就算我把緩衝區設置成幾G,也返回成功,不過實際上怎麼可能設置那麼大

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

四、當選擇TCP的Socket發送數據的時候,TCP中的窗口機制不是能防止發送速度過快的嗎?爲何Socket在出現了WSAEWOULDBLOCK後沒有處理?


這個問題第3個回答:

1.在使用非阻塞模式的狀況下,若是系統發送緩衝區已滿,並示及時發送到對端,就會產生該錯誤,繼續重試便可。

3.若是沒有發完就繼續發送後續部分便可。


這個問題第4個回答:

一、使用非阻塞模式時,若是當前操做不能當即完成則會返回失敗,錯誤碼是WSAEWOULDBLOCK,這是正常的,程序能夠先執行其它任務,過一段時間後再重試該操做。

二、發送與接收不是一一對應的,TCP會把各次發送的數據從新組合,可能合併也可能拆分,但發送次序是不變的。

三、在各類狀況下都要根據send的返回值來肯定發送了多少數據,沒有發送完就再接着發。

四、socket是Windows提供網絡編程接口,TCP/IP是網絡傳輸協議,使用socket是可使用多種協議,其中包括TCP/IP。


這個問題第5個回答:

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

WSAEWOULDBLOCK和粘包都是出如今發送到緩衝區這個過程的


Socket編程 (異步通信,解決Tcp粘包)

前面提到,TCP會出現粘包問題,下面將以實例演示解決方案:

問題通常會出現的狀況以下,假設咱們連續發送兩條兩天記錄("我是liger_zql"):

模擬發送示例:

 

#region 測試消息發送,並匹配協議

 TcpClient client =new TcpClient();

 client.AsynConnect();

 Console.WriteLine("下面將連續發送2條測試消息...");

 Console.ReadKey();

 MessageProtocol msgPro;

  for (int i = 0; i<2; i++)

  {

     msgPro =newMessageProtocol("我是liger_zql");

     Console.WriteLine("第{0}條:{1}", i +1,msgPro.MessageInfo.Content);

     client.AsynSend(msgPro);

  }

  #endregion

 

接收端接受兩條信息會出現以下三種狀況:

1.(1)我是liger_zql(2)我是liger_zql

2.(1)我是liger_zql我是(2)liger_zql

3.(1)我是liger_zql我是liger_zql

經過以上三種狀況,顯然二、3都不是咱們想要的結果。那麼如何處理這中狀況呢?

 

解決方案:經過自定義協議...

咱們能夠以將信息以xml的格式發送出去,列入<protocol>content</protocol>經過正則匹配信息是否完整,若是不完整,咱們能夠先將本次接受信息緩存接受下一次信息,再次匹配獲得相應的結果。

(1)將信息對象轉換成必定格式的xml字符串:

(2)對接收的信息經過正則進行匹配處理:

(3)將該定義的協議換換成信息對象,經過對象獲取本身想要的信息。

結果:

最後運行結果以下

 

附上源碼:SocketProQuests.zip

詳細可參考:http://www.cnblogs.com/zengqinglei/archive/2013/05/14/3078842.html

 

 

附:

關於Socket/TCP粘包、多包和少包, 斷包:http://tsing01.blog.163.com/blog/static/2059572832012716103711125/

 

關於Tcp封包粘包問題:http://www.cnblogs.com/jiangtong/archive/2012/03/22/2411985.html

TCP通信處理粘包詳解http://www.cnblogs.com/smark/p/3284756.html

 

參考:

http://hi.baidu.com/chongerfeia/blog/item/b1e572f631dd7e28bd310965.html

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

http://blog.csdn.net/zhangxinrun/article/details/6721495

相關文章
相關標籤/搜索