ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

游戏服务器中对于发包/收包的个人理解

2020-06-01 21:06:11  阅读:1201  来源: 互联网

标签:收包 标识符 包长 包尾 发包 包头 服务器 数据包 包体


TCP

  • 发包: tcp有自动重传机制,所以一般的包体结构基本是
包体长度 包体数据

······很简单明了,也是我们初学网络编程是所用的结构。那么思考一下,我们发包需要什么信息呢?
其实我们只需两个信息,已发送长度数据包大小。那么结构就变成了

包体信息(已发送长度 + 数据包大小) 包体数据(包长 + 数据)

······有些人可能这里就疑惑了,不是包体数据里面有个包长数据吗?为什么还要多一个数据包大小,呵呵,不要着急。我们接下来慢慢说

  • 包体数据内结构
    ······我在网上看到很多人都是用 包头标识符 + 包长 + 数据 + 包尾标识符 包头标识符 + 包长 + 数据或者包长 + 数据 + 包尾标识符 这样的结构来处理数据包的。包括我身边朋友实际项目中也是用的 包长 + 数据 + 包尾标识符这样的结构。
    ······但是这样会有个问题,那就是如果发生的断包的情况(极少发生,可能发生的情况有:恶意改包,截断数据包。或者对端的代码逻辑错误,或者其他不知名的情况下),我们收到了一个不完整包,但之后的包都是正确的包,里面有包长信息和 部分数据,假设包长为16字节,我们收到包长2字节 + 部分数据 4字节,这时候后面发送的包来了,这时候底层就会把前面的不完整包和后面发来的完整包进行拼接,会导致后面的数据一直是混乱的。
    ······这时候有人出来说了。我们不是有包头包尾标识吗?这样就可以判断是不是有效包啊。那我们来看看。如果是包头标识符 + 包长 + 数据这样的结构。假设这时候我们收到了一个被截断的包,里面有5个字节。1个字节表示包头标识,2个字节表示包长(假设16字节),剩下的2个字节表示数据。然后这时候后面的正常包数据来了。但是这个包只有8个字节,包头标识和包长占3个,剩下5个字节全是数据,假设这个8字节的包就是一个完整包的情况下,那我们又经过了一轮接收,这时候终于接收到了前面那个截断包(包长 + 1)的长度(为何要 + 1?因为我们需要后面一个包的包头标识符验证是否是截断包,如果后面的包过很久才发也是一个问题)。这时候我们检查到 package[pack_len + 1] 的位置却不是本该在那儿的包头标识符,这时候我们才发现之前的数据里包含了截断包,只能把它丢弃。里面还包括了一个完好的包。。。
    包长 + 数据 + 包尾标识符的结构异同。就不多讲了。
    可能是没有想过或者也是不在乎那几个数据包吧。我可能有点强迫症。。说多了

我自己定的包体结构是这样的

namespace network::tcp {
#pragma pack(push)
#pragma pack(1)
	/*
	包体信息
	存储结构:包体信息 + 报文结构
	报文结构: 包头结构 + 包体 + 包尾结构
	*/
	struct pack_info {
		slen_t	slen;	//发送长度
		slen_t	len;	//包长
	};

	/*
	包头结构
	报文结构: 包头结构 + 包体 + 包尾结构
	*/
	struct head_pack {
		char	head;	//包头标识符
		slen_t	len;	//包长
	};

	/*
	包尾结构
	报文结构: 包头结构 + 包体 + 包尾结构
	*/
	struct tail_pack {
		slen_t	len;	//包长
		char	tail;	//包尾标识符
	};
#pragma pack(pop)
}

至于为何包尾也要加个包长,还有为什么需要头尾两端的标识符,大家思考一下?

  • 合包机制
    为了减少服务器压力,通常的做法是将小包合成为大包,本来需要发送几次才能发送完成的数据只需要一次就能完成,这样可以减少部分服务器的压力,但是合成的大包不能超过MTU(最大传输单元),那么MTU到底有多大呢?
    其实根据宽带连接方式的不同,MTU可能不尽相同,如下所示:
    (1). PPPoE/ADSL: 1360-1492
    (2). PPTP VPN: 1400-1460
    (3). L2TP VPN: 1400-1460
    (4). Fixed IP: 1400-1500
    (5). DHCP: 1400-1492
    保险起见,这里我自己定的合包大小为1024字节。那么我们合包之后结构就变成了这样
包体信息(已发送长度 + 数据包大小) 包体数据([包头+ 包体 + 包尾] + [包头+ 包体 + 包尾] + …)

怎么样,这下就知道我们最开始为什么需要包体信息中的数据包大小了吧,因为它是代表整个数据包大小的数据,而不是单个数据包。虽然单个数据包的包头信息中包含了包长数据。但是它只代表了自身的大小。

  • 发送优先级
    在游戏中,游戏数据包会有优先级划分,比如自己的移动数据优先级 > 其他玩家移动数据的优先级,其实这个优先级发送机制很多游戏都有(难怪玩撸啊撸的时候卡了都是自己卡,看队友一点都不卡,可能就用了发送优先级吧)。那么我们为了能让先发送的包优先级更大。我的方法是使用最大堆存储消息队列(deque),我们插入发送数据需要三种信息:
    1. 优先级;
    2. 发送数据;
    3. 发送长度;

······我们就是根据优先级来获取最大堆中指定的优先级消息队列,如果没有则创建一个新的消息队列并加入最大堆。再向指定的优先级消息队列中添加我们插入的发送数据。等到发送的时候再取出使用,发送完成后再删除消息队列中的发送数据。如果消息队列为空了,那么就把它从最大堆中移除。这样就能保证最大堆的顶部一直都是消息优先级最高的数据。

  • 对于环形缓冲区
    我个人是对其产生了不少的疑问,所以并没有在项目使用,我用的是自己写的不定长内存池来申请发送数据使用的内存。申请内存的时候,可以直接申请sizeof(包体信息) + sizeof(包头) + 数据长度 + sizeof(包尾)大小的内存,设置数据的时候直接使用指针指向对应的位置就好了。上面的#pragma pack也是为了我们能利用好每一个字节的空间;

udp

还没开始写呢。别着急,等个十年八年就出来了

标签:收包,标识符,包长,包尾,发包,包头,服务器,数据包,包体
来源: https://blog.csdn.net/qq_28398301/article/details/106324867

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有