欢迎加入QQ讨论群258996829
麦子学院 头像
苹果6袋
6
麦子学院

详解TCP粘包

发布时间:2016-07-05 23:42  回复:0  查看:2998   最后回复:2016-07-05 23:42  
学习网络协议的过程中,我们也许会遇到TCP 粘包的现象,那设呢么是TCP 粘包呢,是什么原因造成的呢?这种问题该怎么解决呢?给大家一个参考。
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 并不存在粘包的问题。
原文来自: cnblogs
您还未登录,请先登录

热门帖子

最新帖子