让我们谈谈游戏服务器开发(下)

发表于2016-08-07
评论0 2.5k浏览

防作弊
  防作弊是一个游戏开发者必须面对的问题,它需要从客户端与服务器两个角度来谈:

服务器
  从服务器部署角度,一般说来游戏服务器只会有接入层暴露给客户端(和暴露在公网一个意思),其他的逻辑层与存储层都只是内网通信,但是有一些可联网的单机游戏会将客户端与服务器整合在一起,比如最著名的war3。这样有一个问题就是,如果不使用中间人(或者也可以称之为OB)建立主机服务器,那么会造成拥有主机权限的玩家会使用一些极端的作弊手段。可参照当年VS竞技平台各种T人外挂,以及后来11平台使用中立虚拟主机后对这种恶意T人外挂的杜绝。
  所以说服务器的部署应该以中立权威的姿态,做好内外网隔离。
  通信方面,服务器与客户端通信需要加密,具体加密到什么级别,用对称还是非对称加密这个得要看具体项目而定。
  服务器端需要校验客户端数据,The server is the man – 摘自UE引擎。
  这是一张游戏类型对应协议处理与服务器校验方式参考图:


游戏类型 (1)
  同步校验就是实时校验,客户端发送请求过来,服务器对请求的合法性进行处理,完毕后回应给客户端请求结果;
  异步校验一般用在逻辑比较复杂的服务器,客户端发送请求,服务端优先转发客户端结果,然后再进行校验(还有一种异步校验必须对客户端多次请求合并在一起校验,所以处理必须是异步的);
  回放校验又可以称为日志回放校验,简单来说就是客户端主导一场战斗过程,并记录日志(最典型的应用就是塔防TD类),战斗结束后上报给服务器进行日志分析。

客户端
  我们从客户端会面临的作弊手段来分析:
  内存修改 – 大致防作弊手段是将关键内存值进行混淆,比如一个整形进行异或存储、或者用加密算法加密后hash存储。具体思路可以参考这里
  变速齿轮 – 需要依赖服务器对请求频率与游戏真实时间校验,其实他是属于hook系统时间相关函数的特例
  函数hook – 客户端可以函数符号表隐藏,但是.so和系统库函数还是比较难处理,这些也是很多黑客的突破点
  客户端破解 – 代码混淆、加密,客户端加壳

一些个人看法
  我们在考虑防作弊的时候,一定要假想作弊者拥有客户端代码,并且了解客户端的所有运行机制。
  有些类型的游戏无法在服务端跑着与客户端相同的逻辑,但是需要以客户端计算出结果为主,然后将结果发送给服务端校验。对于这类游戏基本无法做到防止所有作弊行为,可以建立数据分析模型,对于一些常见的客户端结果进行统计分析,从中分析出异常的客户端结果再进行跟踪分析。
  最后的建议是能把主逻辑放在服务端的尽量放在服务端,一些无法放在服务端或者放在服务端代价很大,需要将主逻辑放在客户端,服务器进行后续校验的情况:客户端做好第一层保护(一般来说这层保护可以杜绝基础的工具使用者),服务端做好第二层校验(核心层,尽量做到能检测出90%的有经验的作弊者),数据分析作为最后一层壁垒(用来发掘一些潜在的作弊者和一二层可能存在的漏洞)。项目初期第一层和第二层一定要完善,随着业务扩展数据分析也是不可缺少的。

TCP or UDP
  对于一款游戏,我们是选择tcp协议还是udp协议?大致根据游戏类型选择可以参考上面的思维图。个人角度来说,不推荐也不排斥tcp和udp混合使用在项目中,混合使用并不是指同时使用,譬如进入核心战斗后从tcp切入到udp模式,出战斗后切回tcp模式。
  有一篇关于tcp与udp同时使用相互影响的文献。
  这里需要强调的是:游戏中的udp分为原始的udp与rudp,rudp协议实现了tcp协议以下几个特性:
1、报文按顺序到达,即tcp流特性;
2、报文选择性可靠,即tcp ack特性,确保报文正确到达,可以选择是否重传未收到ack的报文异或是接收方发请求告诉发送方“我没收到请你再传一份”;
3、心跳,即测试连接存活;
  为什么没有窗口滑动与拥塞控制?因为这对于游戏服务来说没多少必要性;其次是既然所有tcp关键特性我们都实现了,干嘛还造轮子搞一个类似TCP over UDP的东西。

UDP怎么用
  就拿一款MOBA游戏来说,服务器每秒要向游戏内所有玩家同步各种单位位置变化,频率大约在10-20个同步帧每秒(这个标准对于体育类游戏可能要达到30,当然那是端游标准),udp用作状态同步相比tcp有明显的效率优势。这里有一些需要注意的是,udp包大小尽量控制在500字节内,已经不止一篇文献提到过这个坑,具体的原因是很多老式的路由器与交换机没有遵循MTU协议规则。

推荐阅读
  Quake3的网络实现 – http://fabiensanglard.net/quake3/network.php
  云风的rudp实现 – http://blog.codingnow.com/2016/03/reliable_udp.html
  一篇很好的tcp vs udp文章 – http://gafferongames.com/networking-for-game-programmers/udp-vs-tcp/

灵活设计
  先上一张图镇小标题:


  功能不要写死
  日常开发中经常会有这样的情景:
  A:“这个关卡的结算我需要用一个公式来算出结果!”
  B:“公式后面会经常变动吗,是套用到所有关卡吗?”
  A:“会的,不要写死了,我需要可配置的!是不是用到所有关卡还没定,先别写死”
  这里我们可以明确2个需求:一是关卡结算结果由一个公式推导出;二是这个公式最好根据关卡有所有区分。
  最灵活方案就是:将关卡进行分类,根据类型套用不同的公式;公式设置成可配置,配置项包括计算的因子与加减乘除规则。这个方案好处就是一旦开发完成,理想状态策划可以随心所欲的对结算结果进行设计,程序这边不用进行任何二次开发。
  而实际情况有可能就是因为公式配置太灵活,一点点小疏忽会导致各种修改出错,程序协助查错时间也耗去不少。因为在游戏配置这块,越灵活的配置代价就是越高的格式需求,往往牵一发动全身。
  个人不太喜欢在配置中嵌入太多逻辑,喜欢将逻辑解耦放入独立的配置项,尽量互不干扰。对于上述的案例,只将计算因子提出作为配置项,加减乘除关系直接写入代码,我相信独立配置a,b,c,d,e会比配置(a+b)*(c-d*e)出错概率小很多。
  我们再看一个例子:
  A:“我们需要根据玩家战力做一个pvp匹配,具体规则如下……”
  游戏中常见的匹配系统,我们根据需求将玩家战力作为核心参数传递到匹配服务器进行匹配即可。如果哪天过来增加一下需求,我们要做一个积分匹配的,匹配核心规则不变,那岂不是所有接口都还需要添加积分的核心参数…
  建议的方式是将各种参数映射成key,把key传入到匹配服进行匹配,生成key时有意的避免不同维度之间的相关性。下次修改或者增加匹配维度时,匹配核心服务器不用修改,修改生成key的代码便可以。
  这类业务开发灵活性设计更多的是取决于经验,遇见的情况(keng)多了,自然而然就知道在开发中怎么设计方便之门。总体需要注意一点就是,不要过度设计,更高的灵活性换取的代价可能是更高的维护/配置难度和更长的Debug时间,尽量从中取舍平衡。灵活,优雅,简单三者融合。

写在最后
  抽空写了让我们谈谈游戏服务器开发(上)(下)两篇博文,其实要谈的远不止这些,谈及内容也是自己想到哪里说到哪里,不一定写的很全,博文里80%内容都是自己实践过的,20%内容是从文献里学习到的一些技巧与知识点。
  文中内容若与实际偏差、或者观点错误请包涵,欢迎留言指出交流。
  (全文结束)

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引