tcp中的粘包、半包的處理方法

工程師WWW發表於2015-05-12

 TCP粘包是指傳送方傳送的若干包資料到接收方接收時粘成一包,從接收緩衝區看,後一包資料的頭緊接著前一包資料的尾。

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

c++的解決方法如下:

接收端
    int iRecvSize = PackteSize + 10;
    int iRet;
   int idx = 0;
   while (iRecvSize > 0)
   {
    iRet = recv(AcceptSocket, recvbuf+idx, iRecvSize, 0);
    if (iRet > 0)
    {
     idx += iRet;
     iRecvSize -= iRet;
    }
    else if (iRet == 0)
    {
     break;
    }
    else if ( iRet == SOCKET_ERROR)
    {
     break;
    }
   }
傳送端:
   int iSendSize = PacketSize + 10;
   int iSent;
   int idx = 0;
   while (iSendSize > 0)
   {
    iSent = send(m_socket,sendbuffer+idx,iSendSize,0);
    if (iSent > 0)
    {
     idx += iSent;
     iSendSize -= iSent;
    }
    else if (iSent == 0)
    {
     break;
    }
    else if (iSent == SOCKET_ERROR)
    {
     wprintf(L"send failed with error: %d\n", WSAGetLastError());
     //closesocket(m_socket);
     //WSACleanup();
     break;
    }
   }
若傳輸的資料為不帶結構的連續流資料(如檔案傳輸),則不必把粘連的包分開(簡稱分包),反之則必須分包。


c#中用socket的Available成員來表示是否還有資料需要讀取,如果Available>0,表示還有資料需有讀取,反之讀取完成。

  public class ConnectInfo
    {
        public ArrayList tmpList { get; set; }
        public SocketAsyncEventArgs SendArg { get; set; }
        public SocketAsyncEventArgs ReceiveArg { get; set; }
        public Socket ServerSocket { get; set; }
        public User user = new User();

    }


     if (client.Available > 0)
      {
                    Console.WriteLine("粘包處理");
                    for (int i = 0; i < rec; i++)
                        info.tmpList.Add(datas[i]);
                    Array.Clear(datas, 0, datas.Length);

                    datas = new byte[client.Available];
                    e.SetBuffer(datas, 0, datas.Length);
                    client.ReceiveAsync(e);
                   
       }
       else
       {
                    //檢查暫存資料的ArrayList中有沒有資料,有就和本次的資料合併
                    if (info.tmpList.Count > 0)
                    {
                        for (int i = 0; i < rec; i++)
                            info.tmpList.Add(datas[i]);
                        datas = info.tmpList.ToArray(typeof(byte)) as byte[];
                        rec = datas.Length;
                    }

                    //對接收的完整資料進行處理
       }

半包顧名思義,就不是一個完整的包,tcp發出一個段後,它啟動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發這個報文段。
常見的解決方法就是制定規範的資料傳輸格式。


只要控制好 你需要讀取的位元組長度,就可以了 
比如一個包有10個位元組,包頭4個位元組是長度,包體6個位元組是包內容
你首先要讀的就是4位元組,如果不夠就等下一次recv的返回
夠了4位元組,就能得到包的長度,如果超過4位元組,也只取4位元組,多出來的內容是包體的,後面再處理
然後這4位元組裡面的內容,得到是6,表示後面有6個位元組的包體
然後就是讀6位元組,依然是如果不夠就等下一次recv的返回
直到夠了6位元組為止,
如果讀了6位元組還有多餘,就是下一個包的資料

相關文章