TCP拆包方案(C/C++)

消息的结构是【前4个字节的消息头】+【消息体】,消息头的值是消息体的长度

当然这里仅提供一种思路、方案,具体的代码优化,比如像GetBodyBuff函数中存在来回拷贝的情况,可以根据实际情况进行优化,拆包函数如下:

#define MAX_BUFF_SIZE 4096
接口说明:从缓存区中提取消息体
参数说明:
//buff_read,存放tcp数据的缓存区
//buff_read_size,第一个参数缓冲区的大小
//buff_msg存放提取出来的消息体缓冲区,需要手动清空
//buff_msg_size,第三个参数缓冲区的大小
//num_read,引用类型,read的返回值,读取的tcp数据大小
//num_head,消息头部大小,一般是4
//num_body,消息体的大小,从消息头获取
bool GetBodyBuff(char *buff_read,int buff_read_size,
    char *buff_msg,int buff_msg_size,DWORD &num_read,DWORD num_head,DWORD num_body)
{
    static char buff_temp[MAX_BUFF_SIZE];
    bool ret = false;
    //缓冲区长度足够数据包,解析
    if(num_read - num_head >= num_body)
    {
        ret = true;
        //要用memcpy不要用strncpy
        memcpy(buff_msg,&buff_read[num_head],num_body);
        //整理缓冲区
        int num_last_buff = num_read - num_head - num_body;
        if(num_last_buff > 0)
        {
            //整理读缓冲区
            bzero(buff_temp,MAX_BUFF_SIZE);
            memcpy(buff_temp,&buff_read[num_head+num_body],num_last_buff);
            bzero(buff_read,buff_read_size);
            memcpy(buff_read,buff_temp,num_last_buff);
        }
        else
        {
            //刚好一个整包
            bzero(buff_read,buff_read_size);
        }
        num_read = num_last_buff;
    }
    return ret;
}

完整的demo如下(包括模拟粘包、模拟拆包、拆包函数调用方法):

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>

//缓冲区的大小
#define MAX_BUFF_SIZE 4096
#define DWORD unsigned int

bool GetBodyBuff(char *buff_read,int buff_read_size,
    char *buff_msg,int buff_msg_size,DWORD &num_read,DWORD num_head,DWORD num_body);

//防止字节对齐造成干扰,char数组选择4的倍数
struct MsgData1
{
    int num_head;
    char data[4];
    MsgData1()
    {
        //转换为网络字节序
        num_head = htonl(sizeof(data));
        //'\0'占用一个字节
        strcpy(data,"aaa");
    };
};

struct MsgData2
{
    int num_head;
    char data[8];
    MsgData2()
    {
        num_head = htonl(sizeof(data));
        strcpy(data,"8888888");
    };
};

int main()
{
    //模拟tcp消息
    MsgData1 d1,d2;
    MsgData2 d3,d4;
    //模拟对缓冲区的处理
    //调用read后,将数据读取到了buff中,然后拆包
    char buff_read[MAX_BUFF_SIZE];
    char buff_msg[MAX_BUFF_SIZE];

    //模拟粘包
    DWORD num_read = 0;//tcp读取的字节数
    bzero(buff_read,sizeof(buff_read));
    memcpy(buff_read,&d1,sizeof(d1));
    num_read += sizeof(d1);
    memcpy(&buff_read[num_read],&d2,sizeof(d2));
    num_read += sizeof(d2);
    memcpy(&buff_read[num_read],&d3,sizeof(d3));
    num_read += sizeof(d3);
    memcpy(&buff_read[num_read],&d4,sizeof(d4));
    num_read += sizeof(d4);
    printf("server recv data:",buff_read);
    
    while(num_read >= 4)
    {
        //识别消息头[消息体的长度,前四个字节,网络字节序转主机字节序]
        DWORD num_body = ntohl(*((int*)buff_read));
        //对num_body进行校验
        if(num_body>MAX_BUFF_SIZE)
        {
            //违规数据的处理
            printf("num_body > max!%d\n",num_body);
            break;
        }
        else if(num_body <0 )
        {
            //
            printf("num_body <=0 %d\n",num_body);
            break;
        }
        else
        {
            bzero(buff_msg,sizeof(buff_msg));
            if(GetBodyBuff(buff_read,sizeof(buff_read),buff_msg,sizeof(buff_msg),num_read,4,num_body))
            {
                if(0 == num_body)
                {
                    //心跳包
                    break;
                }
                //解析出消息体,并执行业务逻辑
                printf("recv data:%s\n",buff_msg);
                continue;
            }
            else
            {
                //剩余数据已经不足一个整包,退出
                break;
            }
        }
    }
    return 0;
}


接口说明:从缓存区中提取消息体
参数说明:
//buff_read,存放tcp数据的缓存区
//buff_read_size,第一个参数缓冲区的大小
//buff_msg存放提取出来的消息体缓冲区,需要手动清空
//buff_msg_size,第三个参数缓冲区的大小
//num_read,引用类型,read的返回值,读取的tcp数据大小
//num_head,消息头部大小,一般是4
//num_body,消息体的大小,从消息头获取
bool GetBodyBuff(char *buff_read,int buff_read_size,
    char *buff_msg,int buff_msg_size,DWORD &num_read,DWORD num_head,DWORD num_body)
{
    static char buff_temp[MAX_BUFF_SIZE];
    bool ret = false;
    //缓冲区长度足够数据包,解析
    if(num_read - num_head >= num_body)
    {
        ret = true;
        //要用memcpy不要用strncpy
        memcpy(buff_msg,&buff_read[num_head],num_body);
        //整理缓冲区
        int num_last_buff = num_read - num_head - num_body;
        if(num_last_buff > 0)
        {
            //整理读缓冲区
            bzero(buff_temp,MAX_BUFF_SIZE);
            memcpy(buff_temp,&buff_read[num_head+num_body],num_last_buff);
            bzero(buff_read,buff_read_size);
            memcpy(buff_read,buff_temp,num_last_buff);
        }
        else
        {
            //刚好一个整包
            bzero(buff_read,buff_read_size);
        }
        num_read = num_last_buff;
    }
    return ret;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

宇龍_

若帮助到你,希望能给予鼓励!

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

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

打赏作者

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

抵扣说明:

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

余额充值