游戏性能优化,需要从多个层面进行。美术资源层,降低不必要的网格顶点数、使用可硬件加速的贴图格式和合并渲染队列等。策划需求层,需要掌握技术基本原理,规避技术难度高、风险大的游戏需求、使用“障眼法”掩盖技术缺陷等。程序技术层面,尽可能不改变需求下实现更优的算法、同时使用严格的测试用例(如同步坐标还原测试)校对核心算法的准确性。而在本文旨在考虑的网络层性能忧化,除了考虑数据流量大小和更忧化封/解包算法外,还可以从OSI七层网络协议中寻找办法,较容易操作的是网络传输协议,协议代表有TCP、UDP和SCTP。SCTP有良好的特性,但该协议起步晚目前尚未广泛使用,大多应用程序开在TCP和UDP中作出选择。
任何技术都有优缺点,是场景选择技术,而不是技术带着场景走。TCP提供的发送窗口、可靠性、延时ACK、Nagle算法和慢启动与拥塞控制等功能,在特定场景下有时反而成为负担。但是如果数据逻辑有严格的顺序逻辑关系,就应该使用TCP而不是Udp。UDP的网络传输更快,这非常适合实时类型游戏。以下是来自互联网,有关TCP与RUDP在弱网络掉包的情况下传输性能测试,横轴表示RTT(往返时间)、纵轴表完成输出占比量。
右上角的图例尤其突显RUDP在高掉包率网络环境下的优势,RUDP在[50-150]毫秒内完成约70%的数据传输量,而TCP完成的传输量较为平均地分布在各个延时区。再依据另外3个数据图,更能反映出RUDP相比TCP在更短的延时内完成了大部分数据量的传输
总结出TCP以下特性,减弱其传输性能
* 发送窗口
应用层调用Send时,数据并没有立即送出,只是先将数据拷贝到发送缓存区,此后由发送窗口机制分组送出。
* 延时ACK
TCP同时发送多个分组并且会累积ack,以太网最大的数据帧空间是1518字节,而一个ack占用空间约60字节,只占总空间的4%。为了提高宽带利用率,以及降低大量ACK包造成网络拥堵,TCP延迟40ms发送,如果这段时间内有数据发送到对端,则捎带发送ack
* Nagle算法
TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据。Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块,算法要求一个TCP连接上最多只能有一个未被确认的未完成的分段,在该分段ack到达之前不能发送其他的分段。
假如client发送一个http请求个server。这个请求时1600byte,MSS是1460byte。那么就会分成两个TCP包,第一个1460byte,剩下的140byte放在第二个包。第一个包发送到server时,由于server开启了delay ack,所以没有立即ack,又因为server没有收到完整的http请求包,所以也没有立即进行http response,这就导致ack会一直等到40毫秒的delay时间。其实如果client立即发送第二个包,server收到后立即做出http response也不会有问题。问题时client启动了Nagle算法,第一个包没有收到ack,第二个包就不会立即发送出去。两边相互等m,这就是性能问题的核心原因。解决办法是向TCP套接字设置选项TCP_NODELAY关闭Nagle
* 慢启动与拥塞控制
慢启动与拥塞控制都是为防止过多的数据注入到网络,造成网络拥堵。慢启动算法就是在主机刚开始发送数据报的时候先探测一下网络的状况,如果网络状况良好,发送方每发送一次灵气都能正确的接受确认。那么就从小到大的增加拥塞窗口的大小,即增加发送窗口的大小cwnd。然而不断增大的发送窗口会增大网络负载,所以设置一个慢开始门限值ssthresh,当cwnd > ssthresh时,使用拥塞控制算法,停用慢启动算法。
官网主页可以了解到,Raknet初始是为多人对战游戏而设计的网络库,之后得到不断完善并转向商用。2014年宣布在BSD协议下开源(可以自由的使用,修改源代码)。 Raknet除了支持可靠和多通道传输,还包含游戏在应用的通用功能,如http收发、语音收发、NAT穿透、email发送和信息加密等。Unity3d 4.x的版本接入了Raknet,但Unity3d接入得不并完善1。
优点:(源自互联网2,匹配之后的压测会有更好的结论)
* 高性能 在同一台计算机上,Radnet可以实现在两个程序之间每秒传输25,000条信息;
* 支持Window、Android和iOS平台
* 多个传输通道,提高带宽利用率
* 信息加密传输
缺点:(源自互联网3,匹配之后的压测会有更好的结论)
* Raknet理论上可以支持多个客户端和服务器之间每秒4W个消息的ping-pong测试。但是不稳定,如果某些原因导致消息堆积,则会严重影响发送和接受的响应时间,会达到秒级。
* Raknet如果消息超过承受的极限,底层的逻辑上导致不断会恶化卡的现象,表现出现吃内存,底层线程陷入循环,执行效率下降。
* 目前的Raknet版本不支持发送线程,虽然有发送线程的宏,但是打开后编译不过,还未具体继续研究下去。
* 压测过程中不同的客户端连接数对应能支撑的消息数量也会有明显的差异,主要差距来自轮询过程中会raknet底层会进行组包的大大的减少了实际发包的数量
在所有网络库里,使用最频率非send接口莫属。Raknet里,它定义在文件RakPeerInterface.h,由子类RakPeer实现,以下是Send接口签名,其中涉及几个重要概念:发包优先级和传输通道。
virtual uint32_t Send( const char *data,
const int length,
PacketPriority priority,
PacketReliability reliability,
char orderingChannl,
const AddressOrGUID systemIdentifier,
bool broadcast, uint32_t forceReceiptNumber=0)=0;
data
数据缓存指针
length
数据缓存长度
PacketPriority(传输优先级)
PacketPriority | 说明 |
---|---|
IMMEDIATE_PRIORITY | 最高优先级,内部不缓存、不合并数据包立即发送 |
HIGH_PRIORITY | 每发两次IMMEDIATE_PRIORITY, 发送1次HIGH_PRIORITY |
MEDIUM_PRIORITY | 每发两次HIGH_PRIORITY, 发送1次MEDIUM_PRIORITY |
LOW_PRIORITY | 每发两次MEDIUM_PRIORITY, 发送1次LOW_PRIORIT |
注意:HIGH_PRIORITY及以下类型,Raknet内部会缓存数据包10ms后再发送
由此可猜,为了提高带宽使用率,Raknet内部缓存数据包,除非使用IMMEDIATE_PRIORITY
PacketReliability | 说明 | 可收到的序列 |
---|---|---|
UNRELIABLE | 不可靠乱序 | [5, 1, 6] |
UNRELIABLE_SEQUENCED | 不可靠但部分按序 | [5] (6掉包, 1,2,3,4迟到达被丢弃) |
RELIABLE | 可靠乱序 | [5,1,4,6,2,3] |
RELIABLE_ORDERED | 可靠完全按序 | [1,2,3,4,5,6] |
RELIABLE_SEQUENCED | 可靠部分按序 | [5,6] (1,2,3,4迟于5到达被丢弃) |
* OrderingChannel(传输通道)
在多数RGP游戏战斗中,允许英雄移动的同时释放技能。我们可以想像逻辑开发程序员将角色位置和技能数据包,放到相同的UDP socket下可靠发送。使用可靠传输的UDP,当技能掉包需要重发,此时会阻断英雄位置信息送出,除非技能重发成功。按我们的份析,英雄位置与技能在大多情况下,不存在很强的依赖关系。解决这个问题,我们可以创建多个可靠ucp socket,不同类型的数据在不同的socket下发送,但是这增加系统资源消耗,同时客户端网络框架需要编写代码管理多个socket。
Raknet在单个连接上创建了传输通道的概念,可以用来解决上面问题,并且提高数据传输效率。这就是传输通道,但仅在完全按序系列模式(*_ORDERED)与部分按序系列模式(*_SEQUENCED)下有作用,分别支持最大32个通道,即最大64个通道,并且它们之间互不影响。传输通道只面向对发送端,接收端看不到通道概念。(图片来源4)
AddressOrGUID
发送目标设备地址
broadcast
是否广播发送,广播对象不包含systemIdentifier参数指定的目标设备
ForceReceiptNumber(数据包序号)
ForceReceiptNumber = 0,数据包使用Raknet内部自增长序列号。
ForceReceiptNumber > 0,数据包使用这个传入的参数值作为序列号。
返回值
返回数据包使用的序列号
根据数据包的需求特性,利用Raknet的传输模式和传输通道,细化发送规则,压榨性能。还是以RPG游戏玩法为例,以下列出数据包特性,并尝试推理它们的传输模式和传输通道
数据包 | 需求 | PacketReliability | OrderingChannel |
---|---|---|---|
英雄位置 | 只关心最新的角色位置 | RELIABLE_SEQUENCED | 1 |
英雄技能 | 技能连招效果需要严格顺序关系 | RELIABLE_ORDERED | 1 |
英雄生命值 | 缺失的数据包影响对战结果,生命值ui没有明显的过渡只显示最新生命数值 | RELIABLE_SEQUENCED | 2 |
文字聊天 | 严格的对话顺序、缺失的内容可能产生模糊的话题 | RELIABLE_ORDERED | 2 |
快捷聊天 | 错误队友信息顺序并不影响战斗数据, | RELIABLE | 无 |
【英雄位置】与【英雄技能】均在通道1并不冲突,因为它们分别在*_ORDERED和*_SEQUENCED模式上进行的,通道传输仅只在这两个传输模式系列运作,所以RELIABLE模式下的【快捷聊天】没有无传输通道。
不好的设计可能会使业务层与网络层耦合过多,导致此后的游戏扩展受限。现在来讨论客户端业务层与网络层的交互关系,重点考虑何时发包和收到包后如何通知逻辑层。下文大部分内容整理自Raknet官方文档Send packets,原文提及了3种模型,每种都有各自的优缺点,实践过程中应灵活运用。
行为函数内发送
ShootBullet控制层的函数负责角色的射击行为,需要的参数包括:射击ID、射击位置、射击方向。每次外部调用,ShootBullet内部都会发送一次数据包
优点:
统一封装了发包逻辑,不必担心增加的外部调用,会忘记发送数据包
缺点:
然而射击行为与数据包在设计上产生了强耦合关系。本端为了处理对端相同行为的网络包,需要增加一个中间层函数DoShootBullet,参数决定这是一主动还是被动的数据同步,来决定是否发送网络包。另外,如果要增加发送参数(如子弹剩余量),这些参数会污染ShootBullet接口原来的设计原意,当然这些数据可以从全局数据层获取获取。无论如何这类强耦合关系设计时需要十分注意。
待完成
原文:
https://lizijie.github.io/2018/08/13/Raknet%E7%A0%94%E7%A9%B6.html
作者github:
https://github.com/lizijie