动作手游实时PVP技术揭密(服务器篇)

前言

  我们的游戏是一款以忍者格斗为题材的ACT游戏,其主打的玩法是PVE推图及PVP 竞技。在剧情模式中,高度还原剧情再次使不少玩家泪目。而竞技场的乐趣,伴随着赛季和各种赛事相继而来,也深受玩家喜爱,从各直播平台几万到几十万的观众可见一斑。然而,在移动端推出实时PK并不是一蹴而就的,本文将向大家介绍游戏的实时PVP相关技术。

技术选型

  实时PK的表现方式,是将N个玩家的行为快速同步给其它玩家展示并保持一致性的过程。这里面涉及到几个要思考的要点:

  • 同步什么?可以是玩家具体操作(如移动操作),也可以是某按键操作(如方向键),这两者是有些微区别的。
  • 怎么同步?可以选择方式多种,传统的C/S模式,或者是P2P形式,或者是帧同步等。
  • 同步方式?载体可以是TCP/UDP。使用哪个比较靠谱?

  基于以上的考量,在游戏中,使用的是基于可靠UDP的帧同步模型作为实时PVP的技术方案。你问为什么不采用TCP,为什么不用C/S,为什么不上P2P,下文分晓。

实现细则

  这里讲述一些重要细节,以解决众多的Why not问题。

使用帧同步模型

  为什么选择帧同步,直接原因是继承之前AI(机甲旋风)经验,对于ACT类型游戏,我们认为帧同步是不错的方案,主要是能够获得以下好处:

  • 高一致性。对于格斗中的技能连招,如果不精确到帧,会出现一些诡异现象。试想某个浮空下落的角色,可能一方客户端看到已经倒地,另一方在未倒地时接上其它技能,会出现两个结果,即使将其拉扯回,同样奇怪。而帧同步的机制,保证了双方客户端的一致性。
  • 服务器逻辑简化。传统的C/S 架构下,在服务器计算及校验,大量的核心逻辑需要客户端及服务器都实现一遍,使用帧同步可以大大简化及保证服务器的稳定性。
  • 低流量消耗。在移动网络中,用户的流量即金钱,如果我们游戏的核心部分耗流量严重,那让45%1左右的非wifi用户情何以堪呢?
  • 反作弊。讲道理来说,帧同步对于反作弊并不友好,但是有一个简单的做法可以快速反作弊,就是双方数据不一致时(上报的校验数据或者战斗结果),即检测到有人作弊。

  那么,帧同步的过程是如何进行的呢?下图演示了两个客户端PlayerA/PlayerB和Server交互的过程。

  对于客户端而言,不断的上报其行为(action)至服务器,并且等待服务器下发的帧包驱动其逻辑。这种方式是帧锁定同步(lockstep)的一种演化 2
对于服务器来说,固定帧间隔(66ms)将队列中的PlayerA/PlayerB的actions放在一个Frame中,并同步给两个客户端,这似乎和BucketSync类似。

  我常被问到一个问题,对方的卡顿会不会影响我的游戏体验,从以上我们的同步原理中,或许你可以找到答案。

使用UDP代替TCP

  帧同步并对协议层是TCP还是UDP并无要求,但我们打一开始就没考虑TCP直接拥抱UDP,究其原因,若是对TCP的特性稍有了解,大概会背那三字经:“慢启动”,“快重传”,“拥塞避免”(三个字!)。我概括它以下几点不太适合对实时性要求高,对延迟敏感的移动网络游戏:

  • 慢启动算法不适合移动网络的情景,在移动网络环境下信号时好时坏是常态,慢启动会使数据不能及时快速达到对端。
  • 拥塞避免算法不适合移动网络主要原因是其考虑到网络的公平性及收敛性,并且AIMD 算法会使实时性大受影响,延迟明显提升。

  还有TCP协议用于重传的RTO的指数变化及拥塞算法的实现Nagle的缓存等,都是TCP并不太适合高实时性要求的游戏玩法的原因,不再一一列举。

  那么为什么UDP对比TCP更合适呢?多说无益,看一组数据:

  显而易见,在各种网络情形下,UDP的表现(延迟分布)基本上都优于TCP。测试程序在相同的网络环境下,通过设置一定的延迟,丢包率,抖动等,获得TCP/UDP的RTT3 。

  对于P2P的选择,我们放弃的原因是本身UDP打洞并非100%成功,而若打洞失败则仍要走服务器转发,故简化设计考虑,未选择P2P方式去同步。

自己DIY可靠UDP

  上面讲了用什么(UDP)同步什么(帧数据)的问题,有同学要问了,UDP不可靠传输,丢包怎么办?当然,为了数据一致,我们不允许(或许可以允许少量)丢包,TCP的可靠性是由ack/seq+重传去保证的,世面上大多数的可靠UDP实现,也都是类似原理。

  在考察了几个可靠UDP的实现(UDT,ENet等)觉得略显复杂,并且在我们开发时,公司内部的可靠UDP实现未达到可使用阶段,鉴于自己重新造个轮子并不复杂,于是挽起袖子写了起来。

  用于可靠UDP的实现,其UDP协议自定义包头是这样的:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//客户端上行包

typedef struct UdpPkgRecvHead

{

    uint16_t seq; //start from 1

    uint16_t ack; //ack server seq

    uint16_t sid; //player uid

    uint8_t  action_len; //pkg actions contain

}UdpPkgRecvHead;

//...

//服务器下行包

typedef struct UdpPkgSendHead

{

    uint16_t seq; //inc when frame id ++

    uint16_t ack; //ack client pkg‘s seq

    uint8_t frame_len; //pkg frames contain

}UdpPkgSendHead;

  基于以上的定义,可靠性保证的过程大概如图如示:

  客户端在满足以下条件之一后,发包给服务器:

  • 玩家有操作时
  • 发包后间隔(99ms)未收到回包时
  • 收到包一定间隔(99ms)后

  若玩家有操作,则确认信息随着玩家的操作上行包“捎带”至服务器 ; 如果无操作,则固定时间上报确认信息给服务器。客户端的seq每一个操作行为(action)时加1,服务器seq在每一帧数据下发时加1 ,并且双方的RTO 取值不同4

  对于可靠性的保证,可以采用请求重传,而我们使用的是冗余重传。使用冗余重传的一个好处是,简化了麻烦的时序问题,并且收到的每个包都是完整的顺序的。对于网络拥塞情况下的带宽利用优于TCP,它不足之处是流量略微增加了些。下图是冗余重传的过程:

  图解如下:

  Client发Action1过来,记seq=1,服务器未收到。
  Client又新增了Action2,此时新包将同时包含Action1,Action2,并且seq=2。
  Server确认了上一步骤的包,发给Client的包Ack=2表示确认。
  Client由于某些原因(可能延迟等)尚未收到Server的Ack=2的确认,此时新增Action3,并发包seq=3。
  Client再次发Action4时,发现之前已经Ack=2了,故新包将只带Action3,Action4并且seq=4。

  这里演示了冗余传输的过程,服务器对于收到的包,可以根据seq/ack情况动态去除冗余或者丢弃过期包。可能你会觉得全冗余是否不太合适并且有明显优化空间?在实际现网长期运行中,全冗余的冗余率是100%左右,相比于一些可靠传输的重发最近三帧等方式,这种为可靠性付出的代价是合适的并且也提高了更多实时性。

  小结:在刨除一些优化及细节外,这就是可靠UDP的机制,简单有效,开销极小5。经测试及实际线上运行来看,在弱网络环境下的表现也是不错的。

反作弊策略

  实现的技术细节告一段落,接下来谈谈我们的反作弊策略。有些经验是实践下的真知:)
我们知道帧同步的一切都在客户端运算了,服务器能做的显得很有限。我们不知道玩家当前的位置,不知道玩家的技能使用情况,不知道玩家当前血量,拿什么来反作弊?

实时的检测,做了两点:

  客户端固定间隔上报双方血量及技能使用情况,服务器进行记录
  单局结束,上报胜负关系

  基于这两点,我们可知道某一帧玩家的血量是多少,每个人都上报自己的及对方(们)的,双向校验可看出有有无作弊行为。困扰而不得其解的是,当只有两人时,判断谁是作弊者比较麻烦。当两人以上时,可以仲裁。

  我们做了一点容错,当胜负结果异常时,才去进一步检查上报的记录以判断作弊者,判输。而对于上报数据并不一致但是胜负关系一致的情况,记录日志来诊断(容易发生在版本变更时)问题。

  通过实时的检测,基本可以检测到单局中作弊行为(加速,无限CD,锁血等),因为他们最终都导致双方数据不一致而结算不一致或上报血量不一致。

非实时的统计学反作弊方案

  当有一些漏洞可被利用但是一时无法定位的时候,统计学上的反作弊会比较有用。这里说的漏洞是指通过某种行为使对方掉线或者不发包等未知原因导致游戏异常结束的行为。

  我们在游戏结算时,非正常获胜的(掉线等)都会记录下来,并且作用于一个惩罚机制。

  每天通过非正常获胜的次数有上限,达到上限后,其非正常获胜都将不计。
  非正常获胜的次数作用于实时检测逻辑,如果双方数据不一致,非正常获胜次数多的玩家失败。
  非正常获胜次数影响玩家进入匹配,次数越高需要等越久才能开始匹配。

  这个方案在线上发挥过作用,有效阻挡了少部分非正常玩家利用漏洞获益,减少了其影响面。

后话

  上文介绍了游戏的实时PVP的技术实现,这里配一个架构图,看看其外围。

  有两点需要说明:

  • TGW的多通接入支持UDP四层,UDP 服务需要监听所有tunel的ip/port。
  • 帧同步的原理,要求我们必须精细化匹配。游戏中是二进制版本+资源版本一致才相互匹配,可以做到更精细化的根据出战忍者双方客户端数据hash值是否一致进行匹配。

  本文介绍了实时PVP的基础技术,一些优化手段及线上遇到的疑难问题的诊断,篇幅所限,且听下回分解。

时间: 2024-10-27 18:59:28

动作手游实时PVP技术揭密(服务器篇)的相关文章

手游开发攻防——二、基础篇

<手游开发攻防--二.基础篇>已经更新完.主要是通过一个官方的DEMO,来分析Unity3D开发中的一些知识点和应用.注意的事项.大家可以去看看.有什么的可以提出来交流. http://blog.csdn.net/kakashi8841/article/details/39451739

手游包压缩技术引领手游行业实现app页游化

近些年,掌上游戏时代已经成为全民风尚,但身为游戏开发商考虑过手游安装包大小与用户转化率之间的关系吗? 随着手机游戏市场发展愈发壮大,行业发展愈加成熟,手游厂商愈来愈多,手游产业也进入了优胜劣汰的环节,产业的阵痛也凸显了出来.手游安全.用户体验度.优质游戏包体太大.游戏版本更新导致用户流失等等,这些都与手游厂商生存息息相关. 尤其是游戏包体的大小直接关系到了游戏厂商推广渠道的成本问题.想要高质量,意味着包体变大,压缩包体却会有损游戏质量,这样的难题在手游行业由来已久.不同类型的游戏都有其包体体积的

手游开发攻防——二、基础篇(持续更新)

不好意思,最近公司成员扩招,然后技术培训,项目事宜原因,因此这篇文章等到现在才出. 好了,不多说其它. 文章适合人群:对Unity基础组件有一些了解的,想知道怎么在项目中具体应用各种组件. 这篇文章以一个Asset Store上面的例子"Unity Projects Stealth"来讲解Unity的一些知识.所以可能你要对Unity一些概念有个了解.另外,这个例子"Unity Projects Stealth.unitypackage"我已经分享到http://p

Corona手游教程之widget:Slider篇

废话不多说,同Corona SDK其他widget一样,出于节约内存考虑定制化的slider也需要使用ImageSheet,并且不可以伸缩(scale)或通过.width或.height属性改变宽度和高度. 我们创建一个slider的基本分方法是: widget.newSlider( options ) options的公共参数为: id:(可选)一个赋予slider的可选字符串标识.默认值为"widget_slider" x, y:(可选)origin的坐标 left, top:(可

Corona手游教程之widget:PickerWheel篇

首先什么是pickerWheel,如下图所示: 这是移动设备上交互创新的典型控件,非常适合触屏进行选择,对应PC上的下拉框. 在Corona中pickerWheel被设定为320X222像素大小.我们可以使用默认样式或定制化的pickerWheel.另外,请注意列的总宽度实际是280像素,因为要扣除左右两边的20像素边框大小. 为了节约内存,pickerWheel在定制化的时候同样使用Image Sheet.pickerWheel不支持对其宽度和高度进行伸缩(scale)或改变其.width或h

Corona手游教程之widget:Button篇

在corona sdk里,创建界面交互元素widget,非常方便灵活,并且具备极强的可定制性. 我们创建按钮使用如下代码: widget.newButton( options ) 我们有多种方式来创建按钮,不管哪一种,options都可以包含的公共字段如下: id:(可选)一个关联到此按钮的可选的字符串标识,默认为”widget_button“ x, y:(可选)origin坐标 left, top:(可选)左上角坐标 isEnable:是否启用,默认为true.通过button:setEnab

Corona手游教程之widget:ProgressView篇

通常为了节省内存,我们通过ImageSheet来创建进度条(progressView),进度条也不支持伸缩.我们创建进度条的方式如下: widget.newProgressView( options ) options的公共字段包括如下: id:(可选)一个赋予progressView的标识字符串,默认为default_progressView x, y:(可选)widget的位置坐标的x和y left, top:(可选)左上角的坐标 width:进度条的总长度 isAnimated:(可选)如

PVP:手游进程的终点

  PVP是什么? 既然是说PVP,那先从这个世界开始说起.自从有生物诞生到这个世界之后,冲突就一直存在,冲突就是我们所说的PVP.世界上大到一场战争,小到一场打斗,拿到游戏里来说就是单人PVP与团队PVP的区别. 而战争则是推动科技发展的重要因素,莎比诺夫说:“战争是人类最不愿看到的,但是必须要经历,这样促使了人类社会的发展.所以说战争是双面刃.” 在游戏里,我们也可以发现这样的现象同样存在,以<炉石传说>这款游戏来说,如果这款游戏不是以PVP为主,而是刷冒险模式为主,还会有现在毒奶牧.死鱼

实时pvp(皇室战争)网络同步研究

实时pvp的关键在于对抗延迟和网络同步. 这里先分享几篇非常经典的文章 http://www.skywind.me/blog/archives/131 https://www.zhihu.com/question/36258781 http://www.skywind.me/blog/archives/1048 之前我也分享过一篇关于帧同步的思考,这篇文章最后也分享了几篇非常不错的英文文章. http://blog.csdn.net/langresser_king/article/details