tcp粘包拆包解决方案

一  概述

经过上图咱们看到了接收方为了接受这两条连贯的指令,一共作了三次接受,第二次接收的时候,收到了一部分message1的内容和一部分message2的内容。这里要说明几个注意事项: 1. MSS(maxmium  segment size最大报文段大小,TCP层):MSS属性是TCP链接双方在三次握手时所确认的每个TCP报文段中数据字段的最大长度。注意,一是链接双方协商出来的;二是只是数据段的最大长度,不包括IP协议头和TCP协议头的最大长度。 2. 半包是指接收方应用程序在接收信息时,没有接收到一个完成的信息格式块;粘包是指,接收方应用程序在接受信息时,除了接收到发送方应用程序发送的某一个完整数据信息描述外,还接受到了一下发送方应用程序发送的下一个数据信息的一部分。 3. 半包和粘包是针对应用程序来讲的,这个问题只会发生在TCP一些进行连续发送数据时(TCP长链接)。UDP不会出现这个问题,由于UDP都是有边界的数据报;TCP短链接也不会出现,由于发送完一个指令信息后链接就断开了,不会发送第二个指令数据。 4. 半包和粘包问题产生的根本是由于TCP本质上没有“数据块”的概念,而是一连串的数据流。在应用程序层面上咱们所定义的“数据块”在TCP层面上并不被协议承认 5. 半包/粘包是一个应用层问题。要解决半包/粘包问题,就是在应用程序层面创建协商一致的信息还原依据。常见的有两种方式:一是消息定长,即保证每个完整的信息描述的长度都是必定的,这样不管TCP/IP协议如何进行分片, 数据接收方均可以按照固定长度进行消息的还原。二是在完整的一块数据结束后增长协商一致的分隔符(例如增长一个回车符)。

MTU(maxmium transfer unit最大传输单元,处于数据链路层,mac也是数据链路层),46-1500字节大小。如果ip数据包超过1500,会分片。IP层的分片更多的是为传输层的UDP服务。UDP不会分段,就由IP来分。TCP会分段,当然就不用IP来分了

为什么会发生 TCP 粘包、拆包?

  • 要发送的数据大于 TCP 发送缓冲区剩余空间大小,将会发生拆包。
  • 待发送数据大于 MSS(最大报文长度),TCP 在传输前将进行拆包。
  • 要发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次发送出去,将会发生粘包。
  • 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

二 处理粘包分包代码:

  (1)协议:

  (2)代码:

  

#include "stdafx.h"
#include"windows.h"

#include<iostream>

#define CHONG_QING_SIX_LOCK_FRAME_MAX_LENGTH        4096
#define CHONG_QING_SIX_LOCK_DATA_HEADER_LENGTH      6

int RecvData()
{
    DWORD recv_len = 0;
    int dataLength = 0;
    int sumDataLength = 0;
    int nRemainSize = 0;
    int lastPos = 0;
    BYTE recvbuf[CHONG_QING_SIX_LOCK_FRAME_MAX_LENGTH], databuf[CHONG_QING_SIX_LOCK_FRAME_MAX_LENGTH];
    char oneFrameData[1024];

    memset(recvbuf, 0, sizeof(recvbuf));        
    memset(databuf, 0, sizeof(databuf));        

    //收到服务端消息
    //接受数据,处理粘包,拆分包
    recv_len = (int)recv(m_Socket, (char *)recvbuf, CHONG_QING_SIX_LOCK_FRAME_MAX_LENGTH, 0);
    if (recv_len > 0)
    {
        memcpy(databuf + lastPos, recvbuf, recv_len);
        lastPos += recv_len;
        //判断消息缓冲区的数据长度大于消息头
        while (lastPos >= CHONG_QING_SIX_LOCK_DATA_HEADER_LENGTH)
        {
            //包头做判断,如果包头错误,收到的数据全部清空
            if (databuf[0] == 0xEF && databuf[1] == 0xEF && databuf[2] == 0xEF && databuf[3] == 0xEF)
            {
                dataLength = MAKEWORD(databuf[4], databuf[5]);
                sumDataLength = CHONG_QING_SIX_LOCK_DATA_HEADER_LENGTH + dataLength + 6;
                //判断消息缓冲区的数据长度大于消息体
                if (lastPos >= sumDataLength)
                {
                    //CRC校验
                    if (CheckSum((byte *)databuf, dataLength + CHONG_QING_SIX_LOCK_DATA_HEADER_LENGTH + 2))
                    {
                        memcpy(oneFrameData, databuf, sumDataLength);
                        //处理数据
                        DealData(oneFrameData);
                        //剩余未处理消息缓冲区数据的长度
                        nRemainSize = lastPos - sumDataLength;
                        //将未处理的数据前移
                        if (nRemainSize > 0)
                        {
                            memcpy(databuf, databuf + (dataLength + CHONG_QING_SIX_LOCK_DATA_HEADER_LENGTH + 6), nRemainSize);
                            lastPos = nRemainSize;
                        }
                    }
                    else
                    {
                        if (nRemainSize > 0)
                        {
                            memcpy(databuf, databuf + sumDataLength, nRemainSize);
                        }

                        lastPos = nRemainSize;
                    }
                }
                else
                {
                    break;
                }
            }
            else      //寻找下一个包头
            {
                BOOL isFind = FALSE;
                int nFindStart = 0;
                for (int k = 1; k < lastPos; k++)
                {
                    if (databuf[k] == 0xEF && databuf[k + 1] == 0xEF && databuf[k + 2] == 0xEF && databuf[k + 3] == 0xEF)
                    {
                        nFindStart = k;
                        isFind = TRUE;
                        break;
                    }
                }
                if (isFind == TRUE)
                {
                    memcpy(databuf, databuf + nFindStart, lastPos - nFindStart);

                    lastPos = lastPos - nFindStart;
                }
                else
                {
                    memset(databuf, 0, sizeof(databuf));
                    lastPos = 0;
                    break;
                }
            }
        }
    }
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{


    system("pause");
    return 0;
}

三   对以上代码处理流程解释:

  1)可以看到以上代码分为2个缓冲区,第一个只负责接收数据,第二个负责处理数据。

  2)当服务端收到一包数据时,2种情况时,将处理数据的缓冲区的数据全部清空,(1)当出现包头验证错误,(2)CRC校验失败。

  3)当接受到的数据长度大于包头,但是不大于整个消息体的长度时,保存前面接收的数据后,跳出while循环,继续接受数据,

  4)当接收到的数据是多包数据时,它会一直在while循环里面处理数据,直到把每一包数据都分开并处理完后跳出。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

步基

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值