前言

  我们的游戏是一款以忍者格斗为题材的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技术揭密(服务器篇)的更多相关文章

  1. 动作手游实时PVP帧同步方案(客户端)

    1.概述 1.1.基于UDP的帧同步方案 在技术选型方面,之所以选择帧同步方案,在Kevin的一篇介绍PVP帧同步后台实现的文章中已经做了详细叙述,这里简单摘要如下: 高一致性.如果每一帧的输入都同步 ...

  2. GJM :动作手游实时PVP 帧同步(客户端)[转载]

    原帖地址:http://gad.qq.com/article/detail/7171195 原文作者:唐声福  原帖备注:版权所有,禁止匿名转载:禁止商业使用:禁止个人使用. 1.概述 1.1.基于U ...

  3. 手游后台PVP系统网络同步方案总结

    游戏程序 平台类型:   程序设计:   编程语言:   引擎/SDK:   概述 PVP系统俨然成为现在新手游的上线标配,手游Pvp系统体验是否优秀,很大程度上决定了游戏的品质.从最近半年上线的新手 ...

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

    近些年,掌上游戏时代已经成为全民风尚,但身为游戏开发商考虑过手游安装包大小与用户转化率之间的关系吗? 随着手机游戏市场发展愈发壮大,行业发展愈加成熟,手游厂商愈来愈多,手游产业也进入了优胜劣汰的环节, ...

  5. 手游与App测试如何快速转型? —— 过来人科普手游与App测试四大区别

    随着智能设备的普及和移动互联网的兴起,各家互联网巨头纷纷在往移动端布局和转型,同时初创的移动互联网公司也都盯着这个市场希望分一杯羹.在这个大环境下,互联网的重心已经慢慢从Web端转向了移动端,而移动端 ...

  6. 建一座安全的“天空城” ——揭秘腾讯WeTest如何与祖龙共同挖掘手游安全漏洞

    作者:腾讯WeTest手游安全测试团队商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. WeTest导读 <九州天空城3D>上线至今,长期稳定在APP Store畅销排行的前 ...

  7. SLG手游Java服务器的设计与开发——架构分析

    微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经验. ...

  8. 龙之谷手游WebVR技术分享

    主要面向Web前端工程师,需要一定Javascript及three.js基础:本文主要分享内容为基于three.js开发WebVR思路及碰到的问题:有兴趣的同学,欢迎跟帖讨论. 目录:一.项目体验1. ...

  9. 手游录屏直播技术详解 | 直播 SDK 性能优化实践

    在上期<直播推流端弱网优化策略 >中,我们介绍了直播推流端是如何优化的.本期,将介绍手游直播中录屏的实现方式. 直播经过一年左右的快速发展,衍生出越来越丰富的业务形式,也覆盖越来越广的应用 ...

随机推荐

  1. db2、Oracle存储过程引号用法

      在存储过程中,单引号有两个作用,一是字符串是由单引号引用,二是转义.单引号的使用是就近配对,即就近原则.而在单引号充当转义角色时相对不好理解     1.从第二个单引号开始被视为转义符,如果第二个 ...

  2. How to Delete XML Publisher Data Definition Template

    DECLARE  -- Change the following two parameters  VAR_TEMPLATECODE  VARCHAR2(100) := 'CUX_CHANGE_RPT1 ...

  3. 【bzoj1231】[Usaco2008 Nov]mixup2 混乱的奶牛

    题目描述 混乱的奶牛[Don Piele, 2007]Farmer John的N(4 <= N <= 16)头奶牛中的每一头都有一个唯一的编号S_i (1 <= S_i <= ...

  4. HTML5 Canvas 绘图

    首先要注意: <canvas> 元素不被一些老的浏览器所支持, 但被支持于Firefox 1.5+, Opera 9+, 新版本的Safari, Chrome, 以及Internet Ex ...

  5. Python 局部变量与全局变量

    本来以为 局部变量就是在函数/def/class/lambda内部的变量,全局变量就是在之前这些之外的变量.但是,再一次学习Python atm 中应用时发现了一次特例(意外) 字典中 在函数内部改变 ...

  6. django(一)

    Django 自称是"最适合开发有限期的完美WEB框架".本文参考<Django web开发指南>,快速搭建一个blog 出来,在中间涉及诸多知识点,这里不会详细说明, ...

  7. expect实现自动登录

    自动登录主机(ssh) 建脚本item2login.sh,包含如下内容 #!/usr/bin/expect set timeout 30 spawn ssh -p [lindex $argv 0] [ ...

  8. 整理一下Entity Framework的查询 [转]

    Entity Framework是个好东西,虽然没有Hibernate功能强大,但使用更简便.今天整理一下常见SQL如何用EF来表达,Func形式和Linq形式都会列出来(本人更喜欢Func形式). ...

  9. tcp 出现rst情况整理

    正常情况tcp四层握手关闭连接,rst基本都是异常情况,整理如下: 1. GFW 2. 对方端口未打开,发生在连接建立 如果对方sync_backlog满了的话,sync简单被丢弃,表现为超时,而不会 ...

  10. 时间同步ntp服务的安装与配置(作为客户端的配置

    在linux环境下,我们不仅可以自己设置时间,也可以对系统进行时间的同步,比如同步时间到某台物理机上或虚拟机,皆可!接下来我们就以同步时间到某台物理机为例, 一起学习学习. 1.配置本地yum源(挂载 ...