消息的结构是【前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;
}