TCP的封包与拆包

对于基于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 的那种情况,而且采用应答方式增加了通讯量,加重了网络负荷(但是象FTP等协议采用的就是应答方式)。再后来就是对数据包进行封包和拆包的操作。

封包:
封包就是给一段数据加上包头,这样一来数据包就分为包头包体两部分内容了(以后讲过滤非法包时封包会加入"包尾"内容)。

包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,这是个很重要的变量,其他的结构体成员可根据需要自己定义。根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。

拆包:
对于拆包目前我最常用的是以下两种方式:
1、动态缓冲区暂存方式。之所以说缓冲区是动态的是因为当需要缓冲的数据长度超出缓冲区的长度时会增大缓冲区长度。
大概过程描述如下:
A 为每一个连接动态分配一个缓冲区,同时把此缓冲区和 SOCKET 关联,常用的是通过结构体关联。
B 当接收到数据时首先把此段数据存放在缓冲区中。
C 判断缓存区中的数据长度是否够一个包头的长度,如不够,则不进行拆包操作。
D 根据包头数据解析出里面代表包体长度的变量。
E 判断缓存区中除包头外的数据长度是否够一个包体的长度,如不够,则不进行拆包操作。
F 取出整个数据包,这里的"取"的意思是不光从缓冲区中拷贝出数据包,而且要把此数据包从缓存区中删除掉。删除的办法就是把此包后面的数据移动到缓冲区的起始地址。

这种方法有两个缺点:

1)为每个连接动态分配一个缓冲区增大了内存的使用;

2)有三个地方需要拷贝数据,一个地方是把数据存放在缓冲区,一个地方是把完整的数据包从缓冲区取出来,一个地方是把数据包从缓冲区中删除。这种拆包的改进方法会解决和完善部分缺点。

下面给出相关代码.

  先看包头结构定义

#pragma pack(push,1) //开始定义数据包, 采用字节对齐方式
/*----------------------包头---------------------*/
typedef struct tagPACKAGEHEAD
{
 BYTE Version;
 WORD Command;
 WORD nDataLen;//包体的长度
}PACKAGE_HEAD;
#pragma pack(pop) //结束定义数据包, 恢复原来对齐方式

  然后看存放数据和“取"数据函数:

/*****************************************************************************
Description:添加数据到缓存
Input:pBuff[in]-待添加的数据;nLen[in]-待添加数据长度
Return: 如果当前缓冲区没有足够的空间存放pBuff则返回FALSE;否则返回TRUE。
******************************************************************************/
bool CDataBufferPool::AddBuff( char *pBuff, int nLen )
{
    m_cs.Lock();///临界区锁
    if ( nLen < 0 )
    {
        m_cs.Unlock();
        return false;
    }

    if(nLen <= GetFreeSize())///判断剩余空间是否足够存放nLen长的数据
    {
        memcpy(m_pBuff + m_nOffset, pBuff, nLen);
        m_nOffset += nLen;
    }
    else///若不够则扩充原有的空间
    {
        char *p = m_pBuff;
        m_nSize += nLen*2;//每次增长2*nLen
        m_pBuff = new char[m_nSize];

        memcpy(m_pBuff,p,m_nOffset);
        delete []p;
        memcpy(m_pBuff + m_nOffset, pBuff, nLen);
        m_nOffset += nLen;
        m_cs.Unlock();
        return false;
    }
    m_cs.Unlock();
    return true;
}
/*****************************************************************************
Description:获取一个完整的包
Input:Buf[out]-获取到的数据;nLen[out]-获取到的数据长度
Return: 1、当前缓冲区不够一个包头的数据 2、当前缓冲区不够一个包体的数据
******************************************************************************/
int CDataBufferPool::GetFullPacket(char *Buf, int& nLen)
{
    m_cs.Lock();
    if(m_nOffset < m_PacketHeadLen)//当前缓冲区不够一个包头的数据
    {
        m_cs.Unlock();
        return 1;
    }

    PACKAGE_HEAD *p = (PACKAGE_HEAD *)m_pBuff;
    if((m_nOffset - m_PacketHeadLen) < (int)p->nDataLen)//当前缓冲区不够一个包体的数据
    {
        m_cs.Unlock();
        return 2;
    }

    //判断包的合法性
    /* int IsIntegrallity = ValidatePackIntegrality(p);
     if( IsIntegrallity != 0 )
     {
      m_cs.Unlock();
      return IsIntegrallity;
     }
    */
    nLen = m_PacketHeadLen+p->nDataLen;
    memcpy( Buf, m_pBuff, nLen );
    m_nOffset -= nLen;
    memcpy( m_pBuff, m_pBuff+nLen, m_nOffset );

    m_cs.Unlock();
    return 0;
}

前面提到过这种方法的缺点。下面给出一个改进办法,即采用环形缓冲。但是这种改进方法还是不能解决第一个缺点以及第一个数据拷贝,只能解决第三个地方的数据拷贝(这个地方是拷贝数据最多的地方)。第2种拆包方式会解决这两个问题。
环形缓冲实现方案是定义两个指针,分别指向有效数据的头和尾。在存放数据和删除数据时只是进行头尾指针的移动。
用代码来说明。

注:下面的代码是采用一个开源的游戏服务器的代码,我对此代码有所修改。

int CCircularBufferPool::PutData(TCHAR *pData, int len)
{
    if( len <= 0 )
        return 1;

    EnterCriticalSection(&m_cs);
    while(IsOverFlowCondition(len))///判断缓冲区剩余空间是否够存放len长的数据
    {
        BufferResize(len);///若不够,则扩充缓冲区.
    }

    if(IsIndexOverFlow(len))///判断"尾"指针的位置.
    {
        int FirstCopyLen = m_iBufSize-m_iTailPos;
        int SecondCopyLen = len - FirstCopyLen;
        CopyMemory(m_pBuffer+m_iTailPos, pData, FirstCopyLen);
        if (SecondCopyLen)
        {
            CopyMemory(m_pBuffer, pData+FirstCopyLen, SecondCopyLen);
            m_iTailPos = SecondCopyLen;
        }
        else
            m_iTailPos = 0;
    }
    else
    {
        CopyMemory(m_pBuffer+m_iTailPos, pData, len);
        m_iTailPos += len;
    }

    LeaveCriticalSection(&m_cs);
    return 0;
}

void CCircularBufferPool::GetData(TCHAR *pData, int len, bool Delete)
{
    if(len < m_iBufSize-m_iHeadPos)
    {
        CopyMemory(pData, m_pBuffer+m_iHeadPos, len);
        if(Delete==true)
            m_iHeadPos += len;
    }
    else
    {
        int fc, sc;
        fc = m_iBufSize-m_iHeadPos;
        sc = len - fc;
        CopyMemory(pData, m_pBuffer+m_iHeadPos, fc);
        if (sc)
            CopyMemory(pData+fc, m_pBuffer, sc);
        if(Delete==true)
            m_iHeadPos = sc;
        if(m_iHeadPos >= m_iBufSize)
            m_iHeadPos = 0;
    }
}

//
//进行自定义包的解析
//
int CCircularBufferPool::GetFullPacket( TCHAR *Buf, int &nLen )
{
    EnterCriticalSection(&m_cs);
    if(GetValidCount() < m_PacketHeadLen )//当前缓冲区不够一个包头的数据
    {
        LeaveCriticalSection(&m_cs);
        return 1;
    }

    GetData(Buf,m_PacketHeadLen,false);
    PACKAGE_HEAD *p = (PACKAGE_HEAD *)Buf;
    if( (GetValidCount()-m_PacketHeadLen) < (int)p->nDataLen )//当前缓冲区不够一个包体的数据
    {
        LeaveCriticalSection(&m_cs);
        return 2;
    }

    //判断包的合法性
    int IsIntegrallity = ValidatePackIntegrality(p);
    if( IsIntegrallity != 0 )
    {
        LeaveCriticalSection(&m_cs);
        return IsIntegrallity;
    }

    GetData(Buf,m_PacketHeadLen+p->nDataLen,true);
    nLen = m_PacketHeadLen+p->nDataLen;

    LeaveCriticalSection(&m_cs);
    return 0;
}
int CCircularBufferPool::PutData(TCHAR *pData, int len)
{
    if( len <= 0 )
        return 1;

    EnterCriticalSection(&m_cs);
    while(IsOverFlowCondition(len))///判断缓冲区剩余空间是否够存放len长的数据
    {
        BufferResize(len);///若不够,则扩充缓冲区.
    }

    if(IsIndexOverFlow(len))///判断"尾"指针的位置.
    {
        int FirstCopyLen = m_iBufSize-m_iTailPos;
        int SecondCopyLen = len - FirstCopyLen;
        CopyMemory(m_pBuffer+m_iTailPos, pData, FirstCopyLen);
        if (SecondCopyLen)
        {
            CopyMemory(m_pBuffer, pData+FirstCopyLen, SecondCopyLen);
            m_iTailPos = SecondCopyLen;
        }
        else
            m_iTailPos = 0;
    }
    else
    {
        CopyMemory(m_pBuffer+m_iTailPos, pData, len);
        m_iTailPos += len;
    }

    LeaveCriticalSection(&m_cs);
    return 0;
}

void CCircularBufferPool::GetData(TCHAR *pData, int len, bool Delete)
{
    if(len < m_iBufSize-m_iHeadPos)
    {
        CopyMemory(pData, m_pBuffer+m_iHeadPos, len);
        if(Delete==true)
            m_iHeadPos += len;
    }
    else
    {
        int fc, sc;
        fc = m_iBufSize-m_iHeadPos;
        sc = len - fc;
        CopyMemory(pData, m_pBuffer+m_iHeadPos, fc);
        if (sc)
            CopyMemory(pData+fc, m_pBuffer, sc);
        if(Delete==true)
            m_iHeadPos = sc;
        if(m_iHeadPos >= m_iBufSize)
            m_iHeadPos = 0;
    }
}

//
//进行自定义包的解析
//
int CCircularBufferPool::GetFullPacket( TCHAR *Buf, int &nLen )
{
    EnterCriticalSection(&m_cs);
    if(GetValidCount() < m_PacketHeadLen )//当前缓冲区不够一个包头的数据
    {
        LeaveCriticalSection(&m_cs);
        return 1;
    }

    GetData(Buf,m_PacketHeadLen,false);
    PACKAGE_HEAD *p = (PACKAGE_HEAD *)Buf;
    if( (GetValidCount()-m_PacketHeadLen) < (int)p->nDataLen )//当前缓冲区不够一个包体的数据
    {
        LeaveCriticalSection(&m_cs);
        return 2;
    }

    //判断包的合法性
    int IsIntegrallity = ValidatePackIntegrality(p);
    if( IsIntegrallity != 0 )
    {
        LeaveCriticalSection(&m_cs);
        return IsIntegrallity;
    }

    GetData(Buf,m_PacketHeadLen+p->nDataLen,true);
    nLen = m_PacketHeadLen+p->nDataLen;

    LeaveCriticalSection(&m_cs);
    return 0;
}

2、利用底层的缓冲区来进行拆包
  由于TCP也维护了一个缓冲区,所以我们完全可以利用TCP的缓冲区来缓存我们的数据,这样一来就不需要为每一个连接分配一个缓冲区了。另一方面我们知道 recv 或者 wsarecv 都有一个参数,用来表示我们要接收多长长度的数据。利用这两个条件我们就可以对第一种方法进行优化了。
      对于阻塞 SOCKET 来说,我们可以利用一个循环来接收包头长度的数据,然后解析出代表包体长度的那个变量,再用一个循环来接收包体长度的数据。
   相关代码如下:

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

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

  

  其中,m_TcpSock 是一个封装了SOCKET的类的变量,其中的 ReceiveSize 用于接收一定长度的数据,直到接收了一定长度的数据或者网络出错才返回。

int winSocket::ReceiveSize( char* strData, int iLen )
{
    if( strData == NULL )
        return ERR_BADPARAM;
    char *p = strData;
    int len = iLen;
    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;
        return len += ret;
    }

    return returnlen;

  对于非阻塞的 SOCKET,比如完成端口,我们可以提交接收包头长度的数据的请求,当 GetQueuedCompletionStatus 返回时,我们判断接收的数据长度是否等于包头长度,若等于,则提交接收包体长度的数据的请求,若不等于则提交接收剩余数据的请求。当接收包体时,采用类似的方法。
  下面给出相关代码:

enum IOType
{
    IOInitialize,
    IORead,
    IOWrite,
    IOIdle
};

class OVERLAPPEDPLUS
{
public:
    OVERLAPPED   m_ol;
    IOType    m_ioType;
    bool         m_bIsPackageHead;//当前接收的数据是否是包头数据。

    int          m_count;
    WSABUF       m_wsaBuffer;
    int          m_RecvPos;
    char         m_Buffer[1024*8];//此缓冲要尽可能大

    OVERLAPPEDPLUS(IOType ioType)
    {
        ZeroMemory(this, sizeof(OVERLAPPEDPLUS));
        m_ioType = ioType;
    }
};

接收连接后发出的第一个请求,请求接收包头大小的数据.

OVERLAPPEDPLUS *pOverlappedPlus =  new OVERLAPPEDPLUS;
pOverlappedPlus->m_wsaBuffer.buf =  pOverlappedPlus->m_Buffer;
pOverlappedPlus->m_wsaBuffer.len =  PACKAGE_HEAD_LEN;///包头的长度
pOverlappedPlus->m_bIsPackageHead = true;
pOverlappedPlus->m_RecvPos = 0;
pOverlappedPlus->m_ioType = IORead;

DWORD RecvBytes;
DWORD Flags;

Flags = 0;
if (WSARecv(clientSocket, &(pOverlappedPlus->m_wsaBuffer), 1, &RecvBytes, &Flags, &pOverlappedPlus->m_ol, NULL) == SOCKET_ERROR)
{
    if (WSAGetLastError() != ERROR_IO_PENDING)
    {
        delete pOverlappedPlus;
    }
    else
    {
        ///相关的错误处理
    }
}
else
{
    ///相关的错误处理
}

在GetQueuedCompletionStatus所在的函数中:

if( pOverlapPlus->m_ioType== IORead)
{
    if( pOverlapPlus->m_wsaBuffer.len == dwIoSize )
    {
        if( pOverlapPlus->m_bIsPackageHead == true )///接收到的是包头。
        {
            PACKAGE_HEAD *pPackageHead = (PACKAGE_HEAD *)(pOverlapPlus->m_Buffer);

            if(pThis->IsLegalityPackageHead(pPackageHead)==false)///判断是否是合法的包
            {
                closesocket(lpClientContext->m_Socket);
                continue;
            }

            pOverlapPlus->m_bIsPackageHead = false;
            pOverlapPlus->m_wsaBuffer.len = pPackageHead->nDataLen;
            pOverlapPlus->m_RecvPos += dwIoSize;
            pOverlapPlus->m_wsaBuffer.buf = pOverlapPlus->m_Buffer+pOverlapPlus->m_RecvPos;

        }
        else///接收到的是包体
        {
            pOverlapPlus->m_RecvPos += dwIoSize;
            ///这时pOverlapPlus->m_Buffer里就存放了一个完整的数据包,长度为pOverlapPlus->m_RecvPos

            ///继续请求 请求下一个数据包的包头
            pOverlapPlus->m_wsaBuffer.buf =  pOverlapPlus->m_Buffer;
            memset(pOverlapPlus->m_Buffer,0,sizeof(pOverlapPlus->m_Buffer));
            pOverlapPlus->m_wsaBuffer.len =  PACKAGE_HEAD_LEN;
            pOverlapPlus->m_bIsPackageHead = true;
            pOverlapPlus->m_RecvPos = 0;
        }
    }
    else///接收的数据还不完整
    {
        pOverlapPlus->m_wsaBuffer.len -= dwIoSize;
        pOverlapPlus->m_RecvPos += dwIoSize;
        pOverlapPlus->m_wsaBuffer.buf = pOverlapPlus->m_Buffer+pOverlapPlus->m_RecvPos;
    }

    pOverlapPlus->m_ioType = IORead;
    state = WSARecv(lpClientContext->m_Socket, &(pOverlapPlus->m_wsaBuffer), 1, &RecvBytes, &Flags, &pOverlapPlus->m_ol, NULL);
    if ( state == SOCKET_ERROR)
    {
        if(WSAGetLastError() != ERROR_IO_PENDING)
        {
            //关闭套接字 释放相应资源
            continue;
        }
    }
}

三、如何判断包的合法性.

  判断包的合法性可以结合下面两种方式来判断.但是想100%的判定出非法包,只能通过信息安全中的知识来判定了,对这种方法这里不做阐述.
  1、通过包头的结构来判断包的合法性.
   最初的时候我是根据包头来判断包的合法性,比如判断Command是否超出命令范围,nDataLen是否大于最大包的长度.但是这种方法无法过滤掉非法包,当出现非法包时我们唯一能做的就是断开连接,或许这也是最好的处理办法.
   我们可以给一个完整的包加上开始和结束标志,标志可以是个整数,也可以是一串字符串.以第一种拆包方式为例来说明.当要拆一个完整包时我们先从缓冲区有效数据头指针地址搜索包的开始标志,搜索到后并且当前数据够一个包头数据,则判断开始标志和包头是否合法,若合法则根据代表数据长度的变量的值定位到包尾,判断包尾标志是否与我们定义的一致,若一致则这个包是合法的包.若有一项不一致则继续寻找下个包的开始标志,并把下个合法包的前面的数据全部舍弃.
  2、通过逻辑层来判断包的合法性.
  当取出一个合法的包时,我们还要根据当前数据处理的逻辑来判断包的合法性.比如说在登陆成功后的某段时间服务器又收到了同一个客户端的登陆包,那我们就可以判断这个包是非法的,简单处理就是断开连接.

时间: 2024-11-03 21:11:52

TCP的封包与拆包的相关文章

SOCKET 封包和拆包

对于基于TCP开发的通讯程序,有个很重要的问题需要解决,就是封包和拆包.自从我从事网络通讯编程工作以来(大概有三年的时间了),我一直在思索和改进封包和拆包的方法.下面就针对这个问题谈谈我的想法,抛砖引玉.若有不对,不妥之处,恳求大家指正.在此先谢过大家了. 一.为什么基于TCP的通讯程序需要进行封包和拆包. TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想想河里的流水,是连成一片的,其间是没有分界线的.但一般通讯程序开发是需要定义一个个相互独立的数据包的,比如用于登陆的

网络通讯的封包和拆包

封包和拆包(经典收藏)对于基于TCP开发的通讯程序,有个很重要的问题需要解决,就是封包和拆包.自从我从事网络通讯编程工作以来(大概有三年的时间了),我一直在思索和改进封包和拆包的方法.下面就针对这个问题谈谈我的想法,抛砖引玉.若有不对,不妥之处,恳求大家指正.在此先谢过大家了.一.为什么基于TCP的通讯程序需要进行封包和拆包.TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想想河里的流水,是连成一片的,其间是没有分界线的.但一般通讯程序开发是需要定义一个个相互独立的数据包

rtp的封包与拆包h264

请看文档rfc3984 1.看h264的帧 SPS序列参数帧 00 00 00 01 67 64 .... PPS图像参数帧 00 00 00 01 68 EE.... I帧 00 00 00 01 65 EE.... P帧 00 00 00 01 61 E0 ... 2. rtp头 RTP 头格式如下: RTP 头的结构: 0                   1                   2                   3 0 1 2 3 4 5 6 7 8 9 0 1

TCP 组包和拆包算法

/************************************* 文件名: server.c TCP 组包和拆包实现算法 作者: 马中海 QQ: 284358503 Email: [email protected] */ #include <stdlib.h> #include <sys/types.h> #include <stdio.h> #include <sys/socket.h> #include <linux/in.h>

C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)

介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解决Socket粘包.半包问题. 更甚至,某些师德有问题的老师,根本就没跟你说过Socket的粘包.半包问题是什么玩意儿. 直到有一天,你的Socket程序在传输信息时出现了你预期之外的结果(多于的信息.不完整的信息.乱码.Bug等等). 任你喊了一万遍“我擦”,依旧是不知道问题出在哪儿! 好了,不说

TCP粘包,拆包及解决方法

粘包拆包问题是处于网络比较底层的问题,在数据链路层.网络层以及传输层都有可能发生.我们日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中. 什么是粘包.拆包? 假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下: 第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内. 第二种情况,接收端只收到一个数据包,由于TC

关于TCP粘包和拆包的终极解答

关于TCP粘包和拆包的终极解答 程序员行业有一些奇怪的错误的观点(误解),这些误解非常之流行,而且持有这些错误观点的人经常言之凿凿,打死也不相信自己有错,实在让人啼笑皆非.究其原因,还是因为这些错误观点所对应的正确观点不符合人的正常思维习惯,是扭曲人的直观感受的. 有两个错误观点非常之经典,一而再,再而三的出现,就跟韭菜一样,割不完,还越长越多.一是经典的"服务器最多65536个连接"误解,打开链接看介绍.另一个就是这里要讲的TCP"粘包"和"拆包&quo

网络编程-TCP传输数据--封包拆包

网络编程 如果你要开发的程序基于网络工作,要和其他计算机进行数据交互,就需要学会网络编程.请你思考,网络是什么? 计算机之间相互传输数据,首先需要介质,可以是网线.光纤.无线电波,就能通过电(光)信号进行基本的0和1传输,可以被计算机识别. 同样的100个电信号,50个为一组和20个为一组,得到的信息是不同的,每多少位信号表示什么样的信息,比如,这组数据来自谁,要发给哪一台电脑的哪个程序? 这都需要一个标准,而网络的世界是互联互通的,所以标准也得统一,就诞生出了一套互联网协议(Internet

[Go] 轻量服务器框架tcp的粘包问题 封包与拆包

tcp传输的数据是以流的形式传输的,因此就没有办法判断到哪里结束算是自己的一个消息,这样就会出现粘包问题,多个包粘在一起了 可以使用这样一个自定义的形式来解决,一个消息分为 head+body  head包括数据的长度和数据编号 , 长度和编号都是uint32类型 也就是32位 占有4个字节 , 总共head占有8个字节 封装一个消息的结构体,作为一个数据实体,比如下面这个,编号 数据 数据长度  三个属性 package znet type Message struct { Id uint32