码迷,mamicode.com
首页 > 其他好文 > 详细

数据分包

时间:2018-01-17 18:09:08      阅读:195      评论:0      收藏:0      [点我收藏+]

标签:size   就是   str   限制   链接   toolbar   情况下   拷贝   进制   

1数据分包

在TCP收数据的时候可能会有一个很大的包,这时候很有可能一次就收不完,

或者说一次收到多个数据包,

二进制分包常用的就是size+body的方式;

为了避免服务器被恶意的攻击, 一般会规定一个包的大小上线,

如果超过了,立刻关闭该链接. 比如两个字节表示长度就是64kb


第一次就是提交一个请求




2数据分包实现思路

1先设置一块内存,并设置大小是可以读取的内存数据,如果完成了这个请求

就会返回实际读到的大小,处理完后继续投递请求;


2 发送投递请求的默认大小为8192(1024*8)  也可以是4k就是命令的大小

如果要收收的数据包 小与 8k,可能有两种情况

1 收到的数据大小 大于等于 一个包的数据大小(就是说你可能收到了两个数据包)

处理完整的包,将剩下的不足一个包的数据移动到下一次投递,跳过那部分内容即可。

这就是粘包

2收到的数据只有一个字节,不足header长度,保留继续 投递recv.

3 如果要收的数据>8k  那么肯定第一次收不完,所以,要重新分配一个数据包

大小的内存来保存这个数据包,投递请求的时候,根据这个大小来投递,不多收。

因为已经知道这个大的数据包有多大,收完后才重新投递。




首先设置这个包的最大上限  如果超过了这个上限 就认定是恶意 数据包  直接断开连接

//左移16位  就是两个字节最大 然后-1   
//0000000000000001   1000000000000000 -1 1111111111111111
//转换成16进制0xFFFF 十进制  61440+3840+240+15  65535
#define MAX_PAG_SIZE ((1<<16) - 1)  //包的上限制


#define MAX_RECV_SIZE 2048          //一个包的大小

struct io_package {
	WSAOVERLAPPED overlapped;             //重叠结构用于IOCP
	WSABUF wsabuffer;                     //收数据的缓冲区
	int opt; // 标记一下我们当前的请求的类型;  //事件类型 
	int accpet_sock;

	int recv;         //当前io_data 缓冲了多少数据
	unsigned char* long_pkg;  //处理大的数据包的情况 > MAX_RECV_SIZE
	char pkg[MAX_RECV_SIZE]; //投递百分之90游戏命令的大小
};



3现在我规定 前面两个字节是大小 如果只收到一个字节就要继续recv

io_data->recv += dwTrans; //当前读取到数据的总大小
if(io_data->recv < 1)  //收到的数据小于一个字节那就退出  因为包长都获取不到
{
    这里继续recv  在原来缓冲区的上面 内存地址要+io_data->recv
}else{
     //获取 包的长度
pkg_size = (pkg[0] | (pkg[1]<<8));
 
}
return pkg_size ;


4 然后就获取到了包的长度啦  然后就要判断这个长度是不是符合我们的规定

if (pkg_size >= MAX_PAG_SIZE ){ //大于64k
// 异常的数据包 关闭连接 
printf("数据异常........\n"); 
close_session(s);
free(io_data);
break;
}



5 这里就会出现几种情况 

1就是数据包是50个字节,但是你收到70个字节这样就是TCP粘包

TCP粘包是指发送的若干个数据包到接收方因为,流式传输没有边界,

也就是你后面的包会粘在你前面包的尾部,但是无法区分



首先处理粘包的情况,因为包头两个字节是长度,所以很好解决

if (io_data->recv >= pkg_size){ 
        //如果收到的数据大于等于一个包长  
        //这里表示  至少有一个完整的包了  
        
       //在这里可以直接处理第一个包, 因为有pkg_size 就是这个包的长度
       //........这里放处理代码
       
       
       //处理粘包  
       if(io_data->recv > pkg_size){  //如果收到的数据是大于一个
           //直接移动内存
           //把这个包 后面的粘包移动到最前面, 然后长度是 收到的长度  减去 第一个包长
           memmove(io_data->pkg,io_data->pkg + pkg_size,io_data->recv - pkg_size)           
       }
       
       //处理完这个包了
       io_data->recv -= pkg_size;
       
       //这里就要继续recv 
       
       这时候注意 缓冲区的地址 要加上收到的长度, 因为他可能是一个粘包数据,他也可能是0
       io_data->wsabuffer.buf = io_data->pkg + io_data->recv;
	io_data->wsabuffer.len = MAX_RECV_SIZE - io_data->recv;

       
}


2数据包没有收完 

io_data->wsabuffer.buf = recv_buffer + io_data->recv;
//剩下的长度就是总长度-去收到的长度
io_data->wsabuffer.len = pkg_size - io_data->recv;

继续recv



3数据包的长度 大于一个包的长度

//这个指针 默认是指着 普通大小包的缓存
unsigned char* recv_buffer = io_data->pkg;
if (pkg_size > MAX_RECV_SIZE){
    if (pkg_size > MAX_RECV_SIZE){
	//数据包的长度 大于当前缓冲区的长度
	//需要准备更多的缓冲区 来接收这个剩余包
	if (io_data->long_pkg == NULL){
		//动态申请这么多的内存
		io_data->long_pkg = malloc(pkg_size);
	//把已经读到的数据拷贝到 申请的内存里面
		memcpy(io_data->long_pkg, io_data->pkg, io_data->recv);
	}
		//指向新的内存
	recv_buffer = io_data->long_pkg;

}

继续recv
这里也是一样的  在原理的缓冲区上加上你收到的长度
io_data->wsabuffer.buf = recv_buffer + io_data->recv;

//这里就是你接下来 要收取的长度  也就是包长减去 收到的数据
io_data->wsabuffer.len = pkg_size - io_data->recv;








3Json协议的分包

1 使用\r\n来区分一个数据包, 然后如果你的命令里带了\r\n 那就要base64编码

使用json的目的就是为了,脚本语言的使用。


2首先要获得包长 就要获得\r\n


//总收到的长度 +  当前收到的长度
io_data->recved += dwTrans; 

//比较
	if (recv < 2){  //没两个字节不存在\r\n
		return -1;
	}

	*pkg_size = 0;
	for (int i = 0; i < recved - 1; i++){
		if (pkg_data[i] == '\r' && pkg_data[i + 1] == '\n')
		{
			//找到了结尾符  获得长度 因为是从0开始 
			//还有\r在\n前面一个字节
			*pkg_size = (i+2);
			return 0;
		}
	}
	
    这种情况下要判断 收到的数据是不是大于 规定的长度 大于就要关闭连接
    if (io_data->recved >= ((1 << 16) - 1)){
			//超出规定 直接关掉
			close_session(s);
			free(io_data);
			break;
		}
    
    
    //如果当前的收到的长度 大于 普通的长度 
    if (io_data->recved >= io_data->max_pkg_len){
        //重新申请一段内存
        	int alloc_len = io_data->recved * 2;
							//如果这个长度大于了 65535 那么默认给他65535
		alloc_len = (alloc_len > ((1 << 16) - 1)) ?
		    ((1 << 16) - 1) : alloc_len;
		if (io_data->long_pkg == NULL){
				io_data->long_pkg = malloc(alloc_len);
				memcpy(io_data->long_pkg, io_data->pkg, io_data->recved);
			}
		else{
			//释放原来的内存 重新申请内存
			io_data->long_pkg = realloc(io_data->long_pkg, alloc_len);
			}
		io_data->max_pkg_len = alloc_len;
    
    }
    
	
//如果返回不为0 就是没有\r\n结尾 要就要继续接收
io_data->wsabuffer.buf = buf + io_data->recved;
io_data->wsabuffer.len = io_data->max_pkg_len
return;

如果到这就表示 已经有一个完整的包了
//需要判断是不是粘包了 
if (io_data->recved > pkg_sizes){
	//这里表示后面还有一个包
		memmove(pkg_da, (io_data->pkg + pkg_sizes),	
	io_data->recved - pkg_sizes);
}

--总长度减去  一个包长
io_data->recved -= pkg_sizes;
如果为0  就表示所有的包都处理完了
					







数据分包

标签:size   就是   str   限制   链接   toolbar   情况下   拷贝   进制   

原文地址:http://blog.51cto.com/12158490/2062111

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!