1. 程式人生 > >tcp報文黏連及解決方法

tcp報文黏連及解決方法

      TCP報文粘連就是,本來發送的是多個TCP報文,但是在接收端受到的卻是一個報文,把多個報文合成了一個報文。

TCP報文粘連的原因:

1.TCP協議採用了Nagle演算法

    Nagle演算法產生的背景是,當時為了解決傳送多個非常小的資料包時(比如1位元組),由於包頭的存在而造成巨大的網路開銷,也就是糊塗視窗綜合徵(silly window syndrome)。簡單的講,Nagle演算法就是當有資料要傳送時,先不立即傳送,而是稍微等一小會,看看在這一小段時間內,還有沒有其他需要傳送到訊息。當等過這一小會以後,再把要傳送的資料一次性都發出去。這樣就可以有效的減少包頭的傳送次數。

2.接收端從緩衝區裡讀訊息的速度太慢

    即傳送端關閉了Nagle演算法,在接收端讀緩衝區的速度很慢的情況下,還是會產生報文粘連的現象。接收端收到一個TCP報文後,報文放在緩衝區裡,如果這個報文沒有及時被讀取,這時又有新的報文傳送過來,然後才讀取緩衝區的內容時,就會一次讀取2個報文的內容,產生報文粘連的現象。

TCP報文粘連的解決方法:

    1.關閉Nagle演算法。在scoket選項中,TCP_NODELAY表示是否使用Nagle演算法。據說在windows中存在既是關閉了Nagle演算法,Nagle演算法仍然在使用的BUG。不知到現在是什麼情況。

    2.接收端儘可能快速的從緩衝區讀資料。可以改進演算法,或者提高任務的優先順序。

    3.還有一個肯定有用但是比較麻煩的方法。可以在傳送的資料中,新增一個表示資料的開頭和結尾的字元,在收到訊息後,通過這些字元來處理報文粘連。
--------------------- 
 

TCP資料粘連初探

 

  1. 資料粘連現象及定義

在基於TCP的網路程式設計專案中,資料粘連的情況其實很常見,下面,就讓我們來用一個很簡單的例項,來重現一下資料粘連。

例子:在基於TCP的網路環境中,客戶端連續傳送三個資料包給伺服器,

資料包的內容分別是:"1hello!"、"2hemm!"、"3star!"

程式如下所示:

case WM_SOCKET_CLIENT:

{

switch(LOWORD(lParam))

{

case FD_WRITE://傳送網路資料事件

{

send(g_sClient,"1hello!",8,0);

send(g_sClient,"2hemm!",7,0);

send(g_sClient,"3star!",7,0);

break;

}

}

break;

}

伺服器端接收資料:

case WM_SOCKET_SERVER:

{

SOCKET pSock = (SOCKET)wParam;

switch(LOWORD(lParam))

{

……………..

case FD_READ://網路資料包到達事件

{

recv(pSock,recvbuf,1000,0);

break;

}

……………..

break;

}

在recv()處設定斷點,除錯跟蹤程式碼後,發現接收的資料為:

從上述現象中可以發現,當我們在伺服器端接收資料的大小設定成足夠大時,我們就會將客戶端連續三次傳送的資料,一次性讀入了進來,這種現象就是資料粘連。

資料粘連的定義:傳送方傳送的若干包資料,在接收方接收到時成一包,即後一包資料的頭緊接著前一包資料的尾。

2 資料粘連的原因

資料粘連既可能由資料傳送方產生,也可能由資料接收方產生。

   2.1 由傳送方引起的報文粘連

    傳送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,傳送方往往要收集到足夠多的資料後才傳送一包資料。若連續幾次傳送的資料都很少,通常TCP會根據優化演算法把這些資料合成一包後一次傳送出去,這樣接收方就收到了粘包資料。

   2.2由接收方引起的報文粘連

    接收方引起的粘包是由於資料接收方使用者程序不及時接收資料,從而導致粘包現象。這是因為接收方先把收到的資料放在系統接收緩衝區,使用者程序從該緩衝區取資料,若下一包資料到達時前一包資料尚未被使用者程序取走,則下一包資料放到系統接收緩衝區時就緊接到前一包資料之後,而使用者程序根據預先設定的緩衝區大小從系統接收緩衝區取資料,這樣就一次取到了多包資料(圖1所示)。

  粘連的情況

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

   根本原因

     無論是由於資料傳送端的原因還是由於資料接收端的原因抑或是交換機的原因造成的資料粘連,其實問題的本質集中在一點,那就是資料接收端在接受資料的時候,TCP沒有提供保護訊息邊界的措施

     所謂保護訊息邊界,就是指傳輸協議把資料當作一條獨立的訊息在網路上傳輸,接收端只能接收獨立的訊息。也就是說接收端一次只能接收發送端發出的一個數據包。

   而TCP(transport control protocol,傳輸控制協議)是一種面向連線的、流傳輸的協議, 是無保護訊息邊界的, 它把資料當作一串資料流,而不認為資料是一個一個獨立的訊息。這樣就會出現如果傳送端連續傳送資料,接收端有可能在一次接收動作中,會接收兩個或者更多的資料包。

   UDP(user datagram protocol,使用者資料報協議)是面向非連線的,是保護訊息邊界的,它會把每一個傳送的資料包都認為是獨立的訊息。也就是說在UDP協議中,是不會存在資料粘連的情況的。

舉個例子來說,例如,我們連續傳送三個資料包,大小分別是2k, 4k , 8k,這三個資料包,都已經到達了接收端的網路堆疊中,如果使用UDP協議,不管我們使用多大的接收緩衝區去接收資料,我們必須有三次接收動作,才能夠把所有的資料包接收完;而使用TCP協議,我們只要把接收的緩衝區大小設定在14k以上,我們就能夠一次把所有的資料包接收下來,只需要有一次接收動作。

3  資料粘連的解決方案

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

為了避免粘包現象,可採取以下幾種措施。

3.1 傳送方的解決方法

對於傳送方引起的粘包現象,使用者可通過程式設計設定來避免,TCP提供了強制資料立即傳送的操作指令push,TCP軟體收到該操作指令後,就立即將本段資料傳送出去,而不必等待發送緩衝區滿;

3.2 接收方的解決方法

(1)及時接收資料

 通過優化程式設計、精簡接收程序工作量、提高接收程序優先順序等措施,使其及時接收資料,從而儘量避免出現粘包現象;

 (2) 將粘連在一起的報文進行分包處理

          此方法是規定報文資料某一位的內容為該幀報文資料的總長度,接收方先提取出此內容,如果緩衝區中的資料氏度大於等於該長度,則按該內容的長度從緩衝區中提取資料;如果長度不夠則不提取資料,等到長度達到要求時再提取資料。這樣即使m現報文粘連現象,應用程式也會將粘連在一起的資料進行分包處理,不會出現資料丟失無法識別報文ID的情況。下面通過一個具體例子進行詳細說明。

           在實驗線上MCU傳送給DCC的狀態報文長度為84位元組(報文ID為91H),應答報文長度為20位元組(報文ID為81H),接收緩衝區為90位元組。如果狀態報文粘連在應答報文之後,則將使DCC無法收到完整的狀態報文。這種情況連續發生3次之後,DCC將認為任務MCU發生故障,系統將停機,因而結果必然是錯誤的。如果將報文長度放在報文的第一位中,報文ID放在第二位中,則進行分包處理後就不會出現上述的診斷錯誤。處理過程如圖2所示。

3.3 解決方案分析

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

第一種程式設計設定方法雖然可以避免傳送方引起的粘包,但它關閉了優化演算法,降低了網路傳送效率,影響應用程式的效能,一般不建議使用。

第二種方法只能減少出現粘包的可能性,但並不能完全避免粘包,當傳送頻率較高時,或由於網路突發可能使某個時間段資料包到達接收方較快,接收方還是有可能來不及接收,從而導致粘包。

  第三種分包處理的方法雖然不能防止粘連的發生,但是可以完全防止報文粘連對系統產生的影響。

4. 我們的解決方案

在最近的一個基於TCP協議的專案中,我們需要通過網路來傳輸圖片,而一般的圖片均是大於4k的,所以我們必須通過分包的形式來傳輸資料,由於專案的需要,我們定義了一套自己的資料傳輸協議,所以每傳送一個數據包出去之前,我們都會加上自定義的協議頭部資訊,剛開始的時候,我們的處理方法是:

保證每一個傳送出去的資料包大小不超過4k,這樣,當資料傳送端連續傳送多個數據包時,如果資料接收端按照4K的大小來接收資料並進行去除頭部進行解析的話,就會出現將第二個包的前一部分當成第一個包的資料進行讀取,從而會在重組圖片資訊的時候,出現錯誤。

針對上述情況,我們採用以下兩種方法:

  1. 資料傳送端傳送的資料包大小均不大於4k,資料傳送端每傳送一個數據包,就進入等待狀態,直到資料接收端接收到資料包,併發送確認包後,再繼續傳送下個數據包,這樣就可以保證資料接收方使用者程序及時從系統接收緩衝區取走資料,有效的保證了在下一包資料到達前資料都被使用者程序取走,下一包資料放到系統接收緩衝區時就不會緊接到前一包資料之後,從而有效的避免了資料包粘連的情況出現,但是,頻繁的互動會增加網路負擔,
  2. 資料傳送端傳送的每個資料包大小均為4k,當傳送的資料不足4k時,直接填充0,這樣,資料傳送端不需要等待資料接收端的確認包,可以連續傳送資料包,資料接收端每次直接以4k的大小讀取資料進行解析,資訊重組。