命令 sig 注意 就会 编码 缓冲 获取 找到 memcpy
1 数据分包
在 TCP 收数据的时候可能会有一个很大的包, 这时候很有可能一次就收不完,
或者说一次收到多个数据包,
二进制分包常用的就是 size+body 的方式;
为了避免服务器被恶意的攻击, 一般会规定一个包的大小上线,
如果超过了, 立刻关闭该链接. 比如两个字节表示长度就是 64kb
第一次就是提交一个请求
2 数据分包实现思路
1 先设置一块内存, 并设置大小是可以读取的内存数据, 如果完成了这个请求
就会返回实际读到的大小, 处理完后继续投递请求;
2 发送投递请求的默认大小为 8192(1024*8) 也可以是 4k 就是命令的大小
如果要收收的数据包 小与 8k, 可能有两种情况
1 收到的数据大小 大于等于 一个包的数据大小 (就是说你可能收到了两个数据包)
处理完整的包, 将剩下的不足一个包的数据移动到下一次投递, 跳过那部分内容即可.
这就是粘包
2 收到的数据只有一个字节,不足 header 长度, 保留继续 投递 recv.
3 如果要收的数据 > 8k 那么肯定第一次收不完, 所以, 要重新分配一个数据包
大小的内存来保存这个数据包, 投递请求的时候, 根据这个大小来投递, 不多收.
因为已经知道这个大的数据包有多大, 收完后才重新投递.
首先设置这个包的最大上限 如果超过了这个上限 就认定是恶意 数据包 直接断开连接
3 现在我规定 前面两个字节是大小 如果只收到一个字节就要继续 recv
//左移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 缓冲了多少数据
unsignedchar * long_pkg; //处理大的数据包的情况 > MAX_RECV_SIZE
char pkg[MAX_RECV_SIZE]; //投递百分之90游戏命令的大小
};
这里继续recv 在原来缓冲区的上面 内存地址要+io_data->recv
io_data - >recv += dwTrans; //当前读取到数据的总大小
if (io_data - >recv < 1) //收到的数据小于一个字节那就退出 因为包长都获取不到
{
4 然后就获取到了包的长度啦 然后就要判断这个长度是不是符合我们的规定
}else{
//获取 包的长度
pkg_size =(pkg[0]|(pkg[1]<<8));
}
return pkg_size ;
5 这里就会出现几种情况
if (pkg_size >= MAX_PAG_SIZE ) { //大于64k
// 异常的数据包 关闭连接
printf("数据异常........\n");
close_session(s);
free(io_data);
break;
}
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;
2 数据包没有收完
io_data - >wsabuffer.len = MAX_RECV_SIZE - io_data - >recv;
}
继续recv
io_data - >wsabuffer.buf = recv_buffer + io_data - >recv;
//剩下的长度就是总长度-去收到的长度
io_data - >wsabuffer.len = pkg_size - io_data - >recv;
3 数据包的长度 大于一个包的长度
继续recv
//这个指针 默认是指着 普通大小包的缓存
unsignedchar * 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;
}
这里也是一样的 在原理的缓冲区上加上你收到的长度
3Json 协议的分包
io_data - >wsabuffer.buf = recv_buffer + io_data - >recv;
//这里就是你接下来 要收取的长度 也就是包长减去 收到的数据
io_data - >wsabuffer.len = pkg_size - io_data - >recv;
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);
return0;
}
}
如果到这就表示 已经有一个完整的包了
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 就表示所有的包都处理完了
数据分包
来源: http://www.bubuko.com/infodetail-2464989.html