数据分包

简介:

1数据分包

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

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

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

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

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


第一次就是提交一个请求




2数据分包实现思路

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

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


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

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

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

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

这就是粘包

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

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

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

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




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
首先设置这个包的最大上限  如果超过了这个上限 就认定是恶意 数据包  直接断开连接
 
//左移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

1
2
3
4
5
6
7
8
9
10
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 然后就获取到了包的长度啦  然后就要判断这个长度是不是符合我们的规定

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



5 这里就会出现几种情况 

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

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

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



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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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数据包没有收完 

1
2
3
4
5
io_data->wsabuffer.buf = recv_buffer + io_data->recv;
//剩下的长度就是总长度-去收到的长度
io_data->wsabuffer.len = pkg_size - io_data->recv;
 
继续recv



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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//这个指针 默认是指着 普通大小包的缓存
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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//总收到的长度 +  当前收到的长度
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  就表示所有的包都处理完了
                    



 本文转自超级极客51CTO博客,原文链接:http://blog.51cto.com/12158490/2062111,如需转载请自行联系原作者




相关文章
|
数据挖掘 Python 数据采集
带你读《Python金融大数据挖掘与分析全流程详解》之三:金融数据挖掘案例实战1
本书以功能强大且较易上手的Python语言为编程环境,全面讲解了金融数据的获取、处理、分析及结果呈现。全书共16章,内容涉及Python基础知识、网络数据爬虫技术、数据库存取、数据清洗、数据可视化、数据相关性分析、IP代理、浏览器模拟操控、邮件发送、定时任务、文件读写、云端部署、机器学习等,可以实现舆情监控、智能投顾、量化金融、大数据风控、金融反欺诈模型等多种金融应用。
|
JavaScript
Js 实现十六进制颜色值和RGB颜色值转换整理
Js 实现十六进制颜色值和RGB颜色值转换整理
338 0
|
Java 关系型数据库 MySQL
网络安全-JDBC反序列化漏洞与RCE
网络安全-JDBC反序列化漏洞与RCE
538 0
|
域名解析 网络协议 应用服务中间件
服务器主机名值命名规范
服务器主机名值命名规范
724 0
第一百三十四节:“应用层半双工”双机串口通讯的程序框架。
第一百三十四节:“应用层半双工”双机串口通讯的程序框架。
323 0
|
Android开发
获取APP版本号:versionName、versionCode---Android基础篇
获取APP版本号:versionName、versionCode---Android基础篇
2487 0
|
机器学习/深度学习 数据采集 人工智能
Python实现深度神经网络RNN-LSTM分类模型(医学疾病诊断)
Python实现深度神经网络RNN-LSTM分类模型(医学疾病诊断)
Python实现深度神经网络RNN-LSTM分类模型(医学疾病诊断)
|
11月前
【通信协议讲解】单片机基础重点通信协议解析与总结之串口通信(三)
【通信协议讲解】单片机基础重点通信协议解析与总结之串口通信(三)
159 1
|
缓存 网络协议 算法
TCP、UDP是如何流量、拥塞控制的?今天一口气讲透!
TCP、UDP是如何流量、拥塞控制的?今天一口气讲透!
353 2
|
前端开发 Serverless Shell
函数计算操作报错合集之遇到错误信息为HandlerNotFound,该怎么办
在使用函数计算服务(如阿里云函数计算)时,用户可能会遇到多种错误场景。以下是一些常见的操作报错及其可能的原因和解决方法,包括但不限于:1. 函数部署失败、2. 函数执行超时、3. 资源不足错误、4. 权限与访问错误、5. 依赖问题、6. 网络配置错误、7. 触发器配置错误、8. 日志与监控问题。
341 0