昨天问了某位美女姐姐一个逻辑问题。由于楼主嘴太笨,语言表达实在不怎么的。到最后也没有解释清楚,最后放弃口头叙述,特写此博客,供美女姐姐参看。要是再不懂,说明我的文字表达也不怎么的,不知到美女姐姐怎么看。
linux下,tcp并发服务器接收数据时,如果网路阻塞,服务器来不处理接收到的数据,就会
出现网络粘包的现象。那么就需要将特定的数据包(应用层自定义协议)进行分包。
应用层数据包格式:
每一包完整的数据是以7e开头,7e结尾的。
例如:7e....字母数字...7e 就是说头尾7e中间是以字母数字组成的数据段,而且这些数据段中也不可能出现7e。数据包是以hex格式,也就是16进制传输。那么服务器接收到数据之后,也就要以hex格式进行处理。
下面分析下数据包粘包的不同情况:
ps:楼主太笨,想了这些情况不知到死了多少脑细胞,唉!
一个或者两个数据包粘包的情况:
1.以7e开头的数据包
7e......7e
一个完整包 ,正常处理
7e......
一个半包,保存起来等待下半包拼接
7e......7e7e......
一个完整包,一个半包,保存半包,等待下半包拼接
7e......7e..........
一个完整包,一个错包(不知到什么时候会发生)
7e7e........
两个7e开头,需要判断上一次接受是否存储有上半包,如果有将第一个7e拼接,如果没有丢掉第一个7e,将第二个7e的半包进行存储
7e7e......7e
两个7e开头,需要判断上一次接受是否存储有上半包,如果有将第一个7e拼接,如果没有丢掉第一个7e,然后正常处理第二个包
2.不是以7e开头的数据包
..........7e
一个半包的情况,需要与上一次的半包拼接,如果上次没有半包,就丢掉
..........7e7e......
一个下半包,一个上半包,与前一次的上半包拼接成完整包后,保存第二个上半包(如果没有上半包,就丢掉---比较坑!)
..........7e7e......7e
一个下半包和一个整包,与前一次的上半包拼接成完整包,接着正常处理第二个完整包(如果没有上半包,就丢掉---这个更坑!)
虽然楼主只是分析了,上面的最多两个包粘连的情况。可是也可能有很多包粘在一起。楼主在程序处理的时候,用一个接口只处理拼包和整包。另外一个接口进行循环处理,如果第一此解析处理,数据段已经完了就退出。如果没有就继续循环进行处理。
下面附上代码:
stick_bag.h
#ifndef _STICK_BAG_H_ #define _STICK_BAG_H_ #include "main.h" /*粘包不同形式的变量*/ #define S1 1 /*一个完整的包,而且长度刚好*/ #define S2 2 /*一个完整的包,还有数据*/ #define S3 3 /*7e开头的半包*/ #define S4 4 /*拼成一个完整包,下半包只有一个7e*/ #define S5 5 /*拼一个完整的包,还有数据*/ #define SFAULT -1 /*错误包*/ int Stick_Bag(char *recv_buffer,int len,char *half_bag,int *half_len); int Get_Bag(char *buffer,int len,char *bag,int *bag_len,char *half_bag,int *half_len); #endif
stick_bag.c
#include "./include/stick_bag.h" #include "./include/print.h" /* * 加入接收数据队列 对接收到的数据包进行分包出理,防止出现连包现象 */ int Stick_Bag(char *recv_buffer,int len,char *half_bag,int *half_len) { char bag[1024] = {'\0'}; int bag_len = 0; int off = 0; int ret ,i = 0; while(i < len){ ret = Get_Bag(recv_buffer + off,len - off,bag,&bag_len,half_bag,half_len); if(ret == S1){ printf("一个完整的包\n"); printf("bag_len:%d\n",bag_len); print(bag,bag_len); memset(half_bag,'\0',1024); *half_len = 0; return 0; }else if(ret == S2){ off = off + bag_len; i = off; printf("一个完整的包,还有数据\n"); /*加入消息队列*/ print(bag,bag_len); memset(bag,'\0',bag_len); memset(half_bag,'\0',1024); *half_len = 0; }else if(ret == S3){/*半包保存起来*/ printf("半包保存起来\n"); return 0; }else if(ret == S4){ off = off + 1; i = off; /*加入数据队列*/ printf("拼成一个包,下半包只剩一个0x7e:\n"); print(bag,bag_len); memset(bag,'\0',bag_len); memset(half_bag,'\0',1024); *half_len = 0; }else if(ret == S5){ off = off + bag_len - (*half_len);/*拼完包,还有数据,偏移量*/ i = off; /*加入数据队列*/ print(bag,bag_len); memset(bag,'\0',bag_len); memset(half_bag,'\0',1024); *half_len = 0; }else{ return -1;/*close socket,error bag*/ } } } /*处理分包,一个包取出,剩下的继续解析*/ int Get_Bag(char *buffer,int len,char *bag,int *bag_len,char *half_bag,int *half_len) { int i = 0; if(*half_len != 0){ if((buffer[0] == 0x7e) && (buffer[1] == 0x7e)){ printf("buffer[0]:%x\n",buffer[0]); half_bag[*half_len] = buffer[0]; *bag_len = *half_len + 1; memcpy(bag,half_bag,*bag_len); return S4;/*拼成一个完整包,下半包只有一个7e*/ } } if(buffer[i] == 0x7e){/*7e 开头*/ bag[0] = buffer[0]; i ++; while(i < len){ bag[i] = buffer[i]; /*一个完整的包,而且长度刚好*/ if((buffer[i] == 0x7e) && (i + 1 == len)){ *bag_len = len; return S1; }else if((i+1 == len) && (buffer[i] != 0x7e)){ memcpy(half_bag,buffer,len); *half_len = len; return S3;/*7e开头的半包*/ }else if((buffer[i] == 0x7e) && (i < len)){/*一个完整的包,还有数据*/ *bag_len = i + 1; return S2; } i ++; } }else{/*非7e开头*/ printf("非7e开头\n"); if(*half_len != 0){ char tmp[1024] = {'\0'}; while(i < len){ tmp[i] = buffer[i]; if((buffer[i] == 0x7e) && ((i + 1) == len)){ memcpy(half_bag+(*half_len),tmp,len); memcpy(bag,half_bag,(*half_len)+len); *bag_len = (*half_len) + len; memset(half_bag,'\0',(*bag_len)); return 1; }else if((buffer[i] == 0x7e) && (i + 1 < len)){ memcpy(half_bag+(*half_len),tmp,i+1); memcpy(bag,half_bag,(*half_len) + i + 1); *bag_len = (*half_len) + i + 1; memset(half_bag,'\0',*bag_len); return 5;/*拼一个完整的包,还有数据*/ }else if((i + 1 == len) && (buffer[i] != 0x7e)){ printf("错误包\n"); return SFAULT;/*错误包*/ } i++; } }else{ printf("错误包\n"); return SFAULT; } } }
楼主的开发环境:
系统 centos6.4 编译器 gcc4.4.7
以上代码楼主亲测可用,这里就不附测试结果。楼主觉得还有很多不合理的地方,暂时不知道怎么解决。比如:如果一次接受数据没有半包,而这一次接收数据之后不以7e开头的,楼主就把数据包丢掉了。这里可能会丢掉太多有用的包,所以比较坑。希望大家能够提出意见,不喜请大喷,喷喷更健康。