平台类型: | |
---|---|
程序设计: | |
编程语言: | |
引擎/SDK: |
概述
PVP系统俨然成为现在新手游的上线标配,手游Pvp系统体验是否优秀,很大程度上决定了游戏的品质。从最近半年上线的新手游来看,越来越多的游戏把核心玩法重心已经放在pvp多人游戏中,手游朝着更重度、多人实时交互的方向发展。本文主要分为两部分介绍pvp系统,前半部分主要介绍手游后台Pvp的同步方案介绍,第二部分主要介绍天天飞车和现在正在开发当中新赛车手游pvp网络同步方案。 同步机制的一致性问题 同步问题的本质是一致性的问题,在同一局多人游戏的过程中,玩家A看到玩家B的状态,应该跟玩家B自身看到自己的状态相一致。延迟是造成不一致的本质原因,假设理想情况下双方的网络时延都为0,那两者应该是同步的,但是在现实情况中,往往是不可能的,本文讨论的同步机制,就是为了解决一致性问题而产生的,对于不同的游戏类型,不同的团队技术积累,可以根据自身情况采取不同的同步机制技术方案。 根据网络的拓扑结构区分,网络同步通信可以是client- client直连p2p通信,也可以是client-server-client服务器转发通信,client中选取一台当作server转发client间通信。本文以常见的手游后台同步方案机制来区分,分为帧同步方案,位置状态信息同步方案进行阐述。 1.帧同步 原理 设存在玩家A、B、C,服务器Server设为S,假设玩家A、B、C是一个状态机,一开始A、B、C都处于状态S1,这时候服务器S给A、B、C相同的输入I,此时A、B、C经过本地的运算,得到同一个状态S2。 在这短暂的时刻,可以理解成所有玩家从状态S1同步到状态S2,三个玩家便达到同步的目的。只要状态机函数模型Fun,初始S1,输入I是确定的,那么三个玩家得到的结果S2肯定也是确定的。 <ignore_js_op>
如图中所示,玩家A、B、C在T1、T2、T3时刻都会收到服务器发送过来的输入,从而变成相同的状态S1,S2,S3,达到同步的目的。可以想象成这就是个回合制的游戏,每个T1、T2、T3间隔是一个回合,玩家在回合结束的时候,状态是一致同步的。那对于我们游戏来说,服务器的输入可以是玩家在这回合的操作序列,可以是状态信息,都可以,取决于客户端游戏方案的设计。只要这输入到达任何一个客户端那里,能把这数据模拟成真实的游戏场景就可以了。此时您可能会有这样的疑问,如果帧同步比作成一个回合制游戏的话,那会不会出现一卡一卡的情况出现呢。其实是不会的,一般游戏的帧数为30-60帧玩家认为是流畅的,对于帧同步来说,我们把里面的每一次输入的时间间隔足够短,人眼的反应是可以被欺骗的,就好像电影放映一样,一张张连续的图片快速播放,人眼就会感觉是连续发生的,同理帧同步虽然就像是一个回合制游戏,但是只要回合的时间足够短,玩家看起来就像是连续的一样。通常情况下,我们把这个回合称为逻辑帧,逻辑帧的设定可以根据游戏类型,自己打磨决定,一般情况下,4-6渲染帧左右为一逻辑帧比较合理,大概1S的时间内,客户端会收到服务器8-10个逻辑帧输入。 后台实现 1、核心思想 对于后台开发来说,服务器主要起到控制作用,对客户端的帧信息进行输入输出管理,服务器就像是一个时间序列的驱动器,每隔一定间隔,会把在这段时间间隔收集到得客户端的输入,下发广播到所有客户端中去,从而驱动客户端执行帧同步处理,简而言之可以看作服务器在时间轴序列上,收集切片,每隔一定间隔,把这时间切片收集到的数据下发给客户端。 1.对于帧同步来说,数据同步的频率较高,当然是希望越小的网络延迟越佳,由于TCP的滑动窗口和重传机制,导致延时无法控制,因此帧同步一般采用udp进行网络传输。提到udp这里就会衍生出可靠性的问题,对于客户端来说,如果某些udp包没有收到该怎么办呢,这就是帧同步客户端会出现的丢帧的情况,这时候得靠客户端与服务器指定针对性的重传机制. 2.服务器单局中数据首先对每一帧下发客户端的数据进行编号,然后并保存下来,某某客户端网络不佳,中途丢了一些包,可以跟服务器发请求,我现在播放到哪一个序列号的帧了,服务器可以把这个客户端当前序号的帧和客户端缺省的帧一并下发,这样客户端拿到数据后,便可继续通过合帧快播的方式,加速播放,赶上当前时间。这样客户端的表现就是在快放一样。 3.一般来说,帧同步的方案的包量都是比较小的,对于客户端在这个时间间隔没有上传任何数据,服务器也得帮该客户端构造空帧出来,免得其他客户端出现没有输入的情况出现。 4.对于短时间的大量重传,服务器可以选择性的采取合并的策略,减少客户端的瞬间的收包数量。同时也可以利用好不超过mtu的包量大小,尽可能的携带一些之前若干个时间帧的信息,最大限度的把信息push到给客户端,减少客户端申请重传的概率. 2、断线重连 服务器单局可以把所有逻辑帧存储下来,当客户端断线,重新登陆的时候,服务器可以将所有的逻辑帧下发给客户端,客户端拿到所有的逻辑帧后,可以快速在后台跑完全部的逻辑帧,当跑完后,加载到画面,就重新回到游戏单局了。由于断线时,跑的是单局上所有客户端一样的逻辑帧,因此,等到恢复游戏的时候玩家的状态是一致的。 3、反外挂 服务器都是切逻辑帧,没有感知到客户端的逻辑,所以反外挂这块不方便校验,可以从以下两方面着手去校验 1.由于所有客户端的数据都是一致的,可以让客户端根据自身数据算出若干个特征值,严格来说, 所有的客户端算出来的特征值都应该是一样的,因为他们的数据是一样的,当有玩家不一致的时候,可以断定该玩家有作弊的嫌疑。 2.通过单局过程或者完成的时候,汇报统计信息给服务器,服务器通过若干个数据的关联关系,进行数据校验。(有点类似手游单机游戏的校验) 4、特殊关注的点 1.随机性:游戏中不可避免会有随机的逻辑,这时候伪随机就派上用场了,通过下发统一的随机种子,确保每个客户端都产生相同的随机序列。 war3中暴击就是使用的伪随机机制,同样是为了应付帧同步的问题而产生的解决方式。 2.浮点数:浮点数尽可能的避免,还有特殊注意的是,如果用了第三方的库,要确保客户端在不同平台的计算结果是一致的,比方说用了某些物理引擎,在安卓和IOS的平台上会有可能计算出不同的结果,那就要在开发过程中,注意避免使用平台不一致的API了。 3.调试难度 帧同步调试比较困难,需要良好的Log系统,针对不一致的情况能通过Log追溯原因。 尽早的搭建起录像功能通过录像回放可以反复观看逻辑上的不同步,方便问题定位。 在单局中增加debug模式下不一致的检查,当发生不一致时,及时发现,定位原因。如果能引入自动化测试那效果就更佳了。 2、位置同步 原理 位置同步比较好理解,并且最开始一批手游的Pvp也是大多采用位置状态同步的方法。位置同步跟帧同步的最大区别是服务器不在进行切逻辑帧,而是被动的帮助客户端转发位置状态同步包,从而实现同步的目的。比说存在玩家A、B、C,玩家A上报自己的位置和状态给服务器S,服务器S把这个玩家上报的包广播给玩家B、玩家C,玩家B、C收到包后,知道了玩家A的位置和状态,做相应的逻辑。由于存在网络延迟,位置同步的方式会导致接收方收到的包是发送方之前历史时间的包, 存在一定滞后性。需要获得较低的网络延迟,可以通过udp的方式去发送同步包,发送包的频率相对帧同步来说可以低一点,服务器上可以处理跟单局相关的逻辑(使用道具等),能感知到单局中的信息。 后台实现 1.核心思想 位置同步的后台相对容易实现,只需要管理好单局的生命周期就好了,控制单局开始、单局结束,在单局过程中通过UDP传输玩家的位置状态同步包,重要的信息可以通过tcp传输给服务器作逻辑仲裁(使用道具,攻击他人等),客户端收到回包做对应的展示逻辑,只要客户端每秒上报给服务器的包数量足够多,那其他客户端在1s内可以拿到多个同步包,能对该客户端在本地的位置状态做出及时的修正,从而达到各个客户端同步的目的。 2.反外挂 位置同步反外挂是比起帧同步来说是更容易实现的,首先因为重要的数据是跟服务器有交互的,因此服务器单局数据中存储着玩家的数据信息,比如说玩家在当前时间在单局中拥有的道具,玩家在地图上的大概位置。打个比方,客户端经常出现使用自身都不存在的道具,经常发起cs请求给服务器,服务器本身是有玩家身上道具信息的,那可以判定玩家有一定的作弊嫌疑。另外一个例子,赛车游戏,上一个客户端上报的同步包还在赛道的中段位置,下一秒玩家上报自己冲线了,这些后台马上就感知到了,客户端存在作弊嫌疑。 3.断线重连 CS同步的断线重连,要看服务器对单局的参与深度,如果服务器上有大量的玩家当前状态位置信息,那玩家重连回来的时候可以通过服务器下发当前的所有玩家状态位置信息给掉线的玩家,掉线玩家拿到全量信息后,马上能重建单局情景,恢复游戏。如果服务器介入不深,其实这时候就只有客户端才有这个完整时间切片的全量位置状态信息了,服务器可以随机挑选一个网络状况较好的客户端,向该客户端发起拉取全量位置状态信息的请求,就好像是服务器向客户端拉取一个时间切片的请求一样,把该客户端在这个时间点上的所有信息汇总报上服务器,服务器拿到全量信息后,下发给掉线重连的客户端,这样掉线的客户端也能拿到全量的数据信息,并恢复单局。在开发过程中,得与前台协商后,要在开发前期就考虑到恢复机制的需求,客户端数据管理统一模块化。 案例:天飞及新赛车手游PVP系统方案 上文主要阐述理论方法论,下文结合实际项目谈谈方案的实施。 游戏类型很大程度上决定了同步方案的选择,赛车游戏需要瞬时的强交互的情景比较少,只有车与车碰撞相互挤压这瞬间才体现出这种情景的需求。因此从技术选型上一开始就倾向使用CS位置同步了,以前做天飞的PVP系统,由于是线上项目,一个版本的时间就一个月左右,不大可能推翻客户端架构采用帧同步的技术方案,因此天飞的之前的Pvp采用的是CS位置同步技术方案,后台开发思想遵循以下思路进行开发的,稳定—>可扩展—>性能,试想下pvp服务器是一个有状态的服务器,如果外网经常出现不稳定导致服务器无法服务,那将会是同一台机器的上万名玩家同时掉线,这在产品运营层面上是不可以接受的,因此稳定是所有要求的首位。其二现在手游浪潮变化快,一不小心服务器承载就爆满了,可能过一段时间新玩法一出来,一下子又涌到新系统上去了,当初部署的机器就面临空负载问题,因此在解决稳定可靠后,要提高系统的可伸缩性,最后剩下的问题就是性能了。 一、自适应网络切换 为了保证可靠性,针对手机的网络情况作了针对性的优化策略。众所周知,手机网络特点不稳定,尤其是在网络切换过程中,会出现闪断。在wifi环境下延迟大概保证在40-80ms之间,如果是3G网络的话,外网真实的延迟平均在60-200ms之间波动,非常不稳定(取自天飞外网PVP统计数据)。pvp中对于位置状态的同步信息,客户端优先考虑p2p直接发送,通过udp进行发送,对于其他重要的请求,通过tcp与客户端通信,位置同步包。对此,服务器后台跟客户端的通信采用了自适应的网络切换,既能满足对效率的要求,又能保证网络的可靠性。 单局开始前,服务器会跟所有的客户端打通udp通道,并且在开局时把Ip端口都广播给所有客户端,客户端自行进行p2p通信同步位置。当客户端感知到P2p无法通信时,会将网络包通过UDP发送到服务器上,服务器通过UDP进行同步包的转发,当服务器感知到与某玩家udp网络不畅通时,服务器会自动切换到tcp上跟客户端进行同步包的通信。简而言之,最开始客户端相互p2p传递,发现网络不佳,使用服务器udp进行转发,服务器感知到与某一客户端网络不佳,针对该玩家的后续同步包会切换成TCP进行转发。 而这一切的网络切换完全是在网络底层进行触发的,应用层无法感知到。实现的原理类似TCP的ACK确认机制,将以图文结合的形式描述。 <ignore_js_op>
*在网络消息的头部带上两个时间戳,起到关键性作用的两个字段为: m_u32UdpServerTime; //服务器最后给客户端发送udp包时间 (服务器使用该字段判断与客户端是否收到UDP包) m_u32ClientUdpTime; //客户端最后给服务器发送udp包时间 (客户端根据该字段判断与服务器是否收到UDP包) 下面以服务器为例,描述如何感知到客户端与服务器的udp网络不佳的,转而切换通过TCP网络发送消息。 服务器维护两个变量 WaitConfirmTime(待确认收到服务器时间戳) ConfirmTime(已经确认收到服务器时间戳) 最开始服务器像客户端发UDP同步包,此时时刻为t1,可以理解成这个包的Seq为t1,此时服务器变量WaitConfirmTime就变为t1,说明服务器在t1时间发过包给客户端,不知道客户端收到没收到,要等待确认。 当客户端收到刚才的udp包,客户端会在他下一个要发给服务器的任意TCP或者UDP包中回带时间戳回来(图中红色回带Seq:t1),目的是为了让服务器知道刚才t1时间的包,客户端已经确认收到了,当服务器收到这个包时,知道客户端已经收到包了,服务器的变量ConfirmTime就更新为t1.,此时WaitConfirmTime和ConfirmTime是相等的,那么可以认为网络是不错的,因为服务器发出的包,客户端全部都被确认收到了。 实际情况是WaitConfirmTime一直都会比ConfirmTime是要超前的,两者交替增大,因为收到客户端确认肯定没有发出的时间快,判断网络是否尚且良好的标准是,当我要发给该客户端发包的时候,我会先看下该客户端的ConfirmTime、WatiConfirmTime和当前的时间戳三者关系,首先如果WaitConfirmTime大于ConfirmTime(说明还有包客户端没收到,所以没确认)超过一定阈值的时候,则认为服务器一直发的包客户端都没有收到,此时服务器会自动切换成TCP给客户端转发同步位置包,确保客户端能收到。 <ignore_js_op>
<ignore_js_op>
客户端判断与其他客户端p2p网络状况切换,判断与服务器udp的网络状况都是跟上述服务器是同一个道理,只不过使用的是包头的不同的字段而已。 真实外网数据情况:平均单局时长3分钟,一个玩家每秒的同步包频率3-7个,自适应网络切换的阈值为5S,UDP打通率大概在73.2%左右, Wifi玩家占比三分之二左右,在外网完全利用p2p完成单局人数只有7%左右, 完全使用p2p和udp包完成单局的人数为63.2%,掉线率大概在5%上下 通过外网的数据得出一些结论,手机网络的情况还是不太适合使用p2p的方式进行通信,后续新手游的网络开发也没有使用P2P的方式了,而是直接一开始就通过服务器用udp方式进行同步位置转发。手游网络不稳定,闪断会比较多,因此非常有必要做单局内的断线重连。 二、断线重连 断线重连由于服务器没有介入很深,因此当玩家掉线的时候,玩家资源并没有及时进行销毁,而是保留到单局结束的时候再进行统一销毁。(衍生出特殊情况,玩家都掉线了,单局里面没人,因此无法驱动下去,导致资源无法回收,得增加checkinvalid的操作,需要检查长时间没有网络交互的单局,超时便可强行回收资源)。断线重连采取的是向其中一个客户端拿取时间切片的方式恢复单局信息。 三、扩展伸缩性 稳定的问题得到解决,在扩展伸缩性上,由于天飞算是第一批上PVP系统的手游,当时市面上没有太多的参考,因此在上线前作出了充分的压测,并在运维、运营管理方面充分考虑,预埋了多处开关,从而应对营运峰值状况的发生。 1.PVP服务器支持不停机更新,动态添加减少机器服务。 玩家是通过匹配服务器决定登陆到哪台Pvp服务器上进行多人游戏,匹配服务器上相当于是一个center,知道当前所有pvp服务器上的玩家承载,当需要重启某台Pvp服务器的时候,可以通过在这台pvp服务器上刷配置,配置生效后,反向通知center匹配服务器,使得后续玩家不会分配到该pvp服务器上,等到该pvp服务器上的单局都结束了(持续3分钟左右),便可以随意操作该服务器,可以替换程序,可以动态下架等。 这样做的好处是,刚上线的时候遇到外挂问题比较严重,在问题暴露后,马上修改代码添加了针对性的策略,直接不停机在用户毫无感知的情况下,在白天就把全部外网的Pvp服务器重启更新了。 2.影响性能的关键项配置化 a.客户端相关的(每秒同步包的基础数量配置化) 在跟客户端同事协商,在不同情况下每秒发送的同步包数量也是动态变化的,不用使用固定的同步频率,以赛车游戏为例,当我没有任何操作,行驶在直道上时,可以相对应的降低同步频率,当我快速变向或者过弯等情况,可以动态增加同步的包量,总而言之,根据自己游戏场景,动态调节同步频率,从而达到节省同步包量,降低服务器负载。 b.服务器内部的 当时压测发现,服务器主要的瓶颈在于CPU,单进程的服务,人数越多,每秒处理的包量就越大,pvp上的逻辑都不太重,主要的cpu消耗都在打解网络包上,当时天飞使用的是最原始的tconnd,加解密也是server自己做的,加密和解密势必会消耗更多的cpu资源。当时留了开关,针对下行包的位置状态信息,设置了不加密的开关。这个数据即便不加密影响也是最小的。为的就是留有更大的运营余地,在面对运营峰值的时候,所采用的权宜之策。 四、反外挂 从两方面,一方面是处理重要游戏过程都是通过tcp与客户端交互的,服务器为进行校验。另一方面单局过程中,客户端每隔一定时间,会上报该时间段内的统计信息,单局结束后,会上报全局的统计信息,服务器收集到这些统计数据后,会进行后校验。 总结 本文主要阐述了帧同步和CS位置同步的原理和主要实现方法,并针对实际项目,整理了自身在做pvp系统所累积到的一些经验心得,由于个人力量有限,难免有纰漏之处。pvp同步的流畅体验,一方面靠的是技术方案的设计,另一方面还离不开策划程序在某些数值上的精细打磨,在数值打磨的方法论上,没有放之四海而皆准的准则,唯一的方法是根据已有经验不断尝试(比如说,同步包频率的设计,客户端提前预测的算法等等),无论使用的是帧同步方案还是CS位置同步方案,相信也会有许多这样需要跟游戏特性相关的参数需要后期打磨设定,才能获得流畅的多人同步体验。不同的游戏类型、不同的团队技术积累,可能就会选择不同的技术方案。无论是何种技术方案,流畅的pvp体验单靠后台给力实现是远远不够的,归根到底还是后台前台策划团队合力的结果。 |