1. 程式人生 > >TCP粘包

TCP粘包

nag style 解包 alloc www. ace nbsp ast ftp

轉載:https://www.cnblogs.com/liyux/p/5594423.html

TCP粘包現象

TCP粘包通俗來講,就是發送方發送的多個數據包,到接收方後粘連在一起,導致數據包不能完整的體現發送的數據。

TCP粘包原因分析

導致TCP粘包的原因,可能是發送方的原因,也有可能是接受方的原因。

發送方

由於TCP需要盡可能高效和可靠,所以TCP協議默認采用Nagle算法,以合並相連的小數據包,再一次性發送,以達到提升網絡傳輸效率的目的。但是接收方並不知曉發送方合並數據包,而且數據包的合並在TCP協議中是沒有分界線的,所以這就會導致接收方不能還原其本來的數據包。

接收方

TCP是基於“流”的。網絡傳輸數據的速度可能會快過接收方處理數據的速度,這時候就會導致,接收方在讀取緩沖區時,緩沖區存在多個數據包。在TCP協議中接收方是一次讀取緩沖區中的所有內容,所以不能反映原本的數據信息。

解決TCP粘包

分析了產生TCP粘包的原因之後,針對發生的原因,針對性的采取解決方法。

禁用Negle算法

因為TCP協議采用Negle算法,導致粘包。所以可以禁用Nagle算法。

const char chOpt = 1;
int nErr = setsockopt(m_socket, IPPROTO_TCP, TCP_NODELAY, &chOpt, sizeof(char));   
if(nErr == -1)
{
    TRACE( "setsockopt() error\n",  WSAGetLastError());
    return ;
}

這種方法雖然能一定程度上解決TCP粘包,但是並不能完全解決問題。因為接收方也是可能造成粘包的原因,這種方法只是發送方有效。而且禁用Nagle算法,一定程度上使TCP傳輸效率降低了。所以,這並不是一種理想的方法。

設置PUSH標誌

PUSH是TCP報頭中的一個標誌位,發送方在發送數據的時候可以設置這個標誌位。該標誌通知接收方將接收到的數據全部提交給接收進程。這裏所說的數據包括與此PUSH包一起傳輸的數據以及之前就為該進程傳輸過來的數據。
當Server端收到這些數據後,它需要立刻將這些數據提交給應用層進程,而不再等待是否還有額外的數據到達。
設置PUSH標誌也不能完全解決TCP粘包,只是降低了接收方粘包的可能性。實際上現在的TCP協議棧基本上都可以自行處理這個問題,而不是交給應用層處理。所以設置PUSH標誌,也不是一種理想的方法。

自定協議

自定協議,將數據包分為了封包和解包兩個過程。在發送方發送數據時,對發送的數據進行封包操作。在接收方接收到數據時對接收的數據包需要進行解包操作。
自定協議時,封包就是為發送的數據增加包頭,包頭包含數據的大小的信息,數據就跟隨在包頭之後。當然包頭也可以有其他的信息,比如一些做校驗的信息。這裏主要討論TCP粘包的問題,所以不考慮其他的。

發送方封包

PACKAGE_HEAD pPackageHead; //PACKAGE_HEAD 包頭結構體
char PackageHead[1024];
int headLen = sizeof(PACKAGE_HEAD);
int packgeContextLen = strlen(packageContext); //packageContext 發送的數據
pPackageHead->nDataLen = packgeContextLen; //包的大小

char *packge = (char*)malloc(headLen + packgeContextLen); //包的內存分配
memset(packge, 0, headLen + packgeContextLen);
char *packgeCpy = (char*)memcpy(packge, (char*)&pPackageHead, headLen);//拷貝包頭
packgeCpy += headLen;
packge = (char*)memcpy(packgeCpy, (char*)&packageContext, packgeContextLen);//拷貝包內容

int ret = 0;
ret = send(m_hSocket, packge, headLen + packgeContextLen, 0); //發送包
if (ret == SOCKET_ERROR || ret == 0)
{
    return ret;
}

接收方解包

char PackageHead[1024];
char PackageContext[1024*20];

int len;
PACKAGE_HEAD *pPackageHead; //PACKAGE_HEAD 包頭結構體
while( m_bClose == false )
{
    memset(PackageHead, 0, sizeof(PACKAGE_HEAD));
    len = ReceiveSize(m_TcpSock, (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 = ReceiveSize(m_TcpSock, (char*)PackageContext,pPackageHead->nDataLen);
    }
}

接收指定長度的數據函數

//接收指定長度的數據
int ReceiveSize(SOCKET m_hSocket, char* strData, int gLen)
{
    if(strData == NULL)
        return ERR_BADPARAM;
    char *p = strData;
    int len = gLen;
    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;
}

這樣就可以達到解決TCP粘包的問題。在實際使用中包頭還帶有更多的信息,而且包尾可能還會帶上分隔符,在redis、FTP中就是這樣處理的。

UDP不存在粘包

由於UDP不是面向‘流’的,而且UDP是具有消息邊界的。也就是說UDP的發送的每一個數據包都是獨立的。所以UDP並不存在粘包的問題。

返回 網絡編程

TCP粘包