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,如需转载请自行联系原作者