夜晚越黑暗,星星就越明亮--布隆
很多游戏存在过被刷资源(装备、道具、经验元宝等)的bug,一旦资源被刷,就会破坏游戏平衡,不仅影响公司名誉,补救措施也会带来不少损失,比如可能要停机紧急维护,可能要回档(游戏数据还原),可能要封号,可能要补偿其他未刷的玩家,无论哪种操作,都可能造成玩家的流失,而如果是大R(高人民币玩家)的流失,影响则更严重,因为公司会少了充值收入了,而补偿也会导致一些玩家暂时不会充钱了。所以写好一个健全强壮的功能至关重要。
游戏被刷的根本原因就在于程序逻辑本身的不健壮,安全检查做少了,以致前端可绕过某些限制,频繁请求该玩法协议,导致该玩法奖励无限发放,就出现了资源被刷的现象。
所以资源被刷离不开两点:
一、安全检查做少了;
二、客户端频繁请求。
比如某项奖励被领取之后,就应记录它已被领取的状态,当客户端再次请求领取时,先判断它是否已被领取,如果已领取了,那就不再发放。但是,即便这样做了,它就一定安全了吗?未必。如果发奖的逻辑写在前,记录领奖状态的逻辑写在后,因为发奖的资源可能要发放到多个功能模块去(比如元宝可能要发放到角色模块,装备要发放到装备模块,道具要发放到道具模块,这样的目的是逻辑解藕),或在发奖和记录状态逻辑之间加了其它业务逻辑,如果发生道具发奖异常或其它业务逻辑异常,后面的记录状态逻辑就不会执行了,但奖励已发了一半或全部发了,因此当客户端再次请求时,因为没记录已领奖状态,那么仍可领部分或全部奖励,然后再报道具发奖异常或其它业务逻辑异常,仍然没记录领奖状态,如此循环,就出现资源被刷了。因此,在写领奖逻辑时,要遵循一个原则:先判断领奖状态,再做领奖记录,最后才发奖。发奖异常补偿总比被刷好。
上述是获得资源时遵循的原则,但在消耗资源时,原则顺序却要变一下,不然也会导致漏扣或不扣资源的情况,变成了变相被刷。比如我要消耗一些材料去强化某件装备,强化成功,装备强化等级加1,属性获得提高。首先判断材料是否足够,这没毛病。但是如果再做强化成功记录,再扣资源,这就可能有问题了,因为扣材料时有可能发生异常,导致少扣或没扣材料,而强化却已记录成功了,客户端循环如此请求,就变成刷其它养成系统了,也是一种变相刷资源,因此,在写消耗资源逻辑时,应遵循的原则是:先判断是否足够,再做资源扣除,最后做养成记录。关键是要防止被刷。
以上是两个常见被刷的地方,除此之外,还有很多业务逻辑也可能被刷,不胜枚举。比如,上面消耗资源判断逻辑时,也不一定是安全的,比如我在商店要用银两买一个道具,假设服务端存储银两是用一个int来存储的,请求购买时需传一个int的数量过来,而配置中,该商品的单价也是一个int值,在购买时确实判断了身上的银两是否小于数量x单价的总量,这看起来是没错啊,但是忽略了一点,计算机在计算int*int时,可能溢出为一个负数了,所以我身上的银两数不可能小于一个负数,这样就通过了判断逻辑,最后扣除银两时,减去一个负数,银两不减反增了,这也出现被刷了。所以这种情况,还需判断最后的总价应该要是一个大于0的值。
要写一个健壮的逻辑,在写的时候,我们就应多考虑下客户端作弊的情况,想下如果客户端频繁请求该协议会出现什么样的现象?如果客户端随意更改请求协议字段的值又会怎样?你要相信一定会有这样的牛人绕过客户端的限制做到随意请求协议随意更改协议内容的,再举两个曾经改协议被刷的例子,比如一个爬塔玩法,每成功爬一层,就会有宝箱奖励,当时协议内容是请求要打的层数,奖励的逻辑是,第一,要打赢了才能领,第二,要宝箱没被领才可领,成功则爬塔层数加1,宝箱状态重置,这种在正常的玩法下不会出事,但是一旦改协议,这样的逻辑就会被刷了,因为我爬完后我可以改协议重新请求从第一层爬啊,这样奖励就重新被刷了,要防止被刷,就应记录每层的领奖状态,然而就算记了每层奖励状态,也不完美,玩家还可以从第一层打,虽然不能刷奖了,但可增加服务器消息量变相攻击服务器啊,因此要么判断请求的层数不能小于当前已打到的最高层数,要么就不让前端传要打的层数,因为后端自己是知道玩家当前打到多少层的,这就是做逻辑(定协议)时的另外一个原则,尽量让前端少传已知的数据上来。
还有同样的一个例子,使用物品时,协议内容是该物品的唯一编码和该物品的配置id及数量,结果在判断物品有没有和判断物品足不足够的检查中通过了,而且该协议里的配置id也确实是使用时规定消耗的物品id,但是扣除物品时,却扣了另外一个廉价的道具,原因是通过唯一编码取得物品时服务端没有判断该唯一编码对应的物品的配置id是否和协议上传的物品配置id相等了,而是用协议上传的配置id通过了是正确规定需要的材料,结果绕过了安全检查,导致实际扣的却是另一种背包里的廉价道具,这例子足矣说明玩家的“机智”,同样的,也说明尽量少传服务端的已知的数据,上面例子,如果不传配置id,服务端以唯一编码取得该物品时就已知该物品的配置id了,用它与规定的配置id对比即可,而不需要用前端上传的配置id与规定的配置id一致来判断。通过上面两个例子可以知道,有些玩法在正常玩耍时,不会有事,毕竟会改协议的人少,但一旦被改协议,就可能出现被刷的风险。
被刷防不胜防而逻辑又难做到万无一失,要如何才能防止被刷呢?除了上述几个原则,及写功能时多想象协议被重复请求或协议被修改请求会有什么后果时,我们还有一些预防方案可以避免:
一、协议安全防范
给协议做简单加密即可,不需那么复杂,影响效率,如每个客户端和服务端都维护一个自己的协议序号,如果协议序号不相等,便可能是造假协议,丢弃。再比如给协议做简单的循环冗余校验。有些人说的给协议做非对称加密算法加密,还不如先等游戏火起来再说。
二、做邮件预警
基本思路是记录在线玩家每天获得的道具总量,然后在道具表和装备等表策划配置一个预期一天最多正常获得的数量,如果玩家获得的超过了这个数量,就发邮件到管理人员报警,针对数额巨大的可写踢下线或封号处理逻辑,核查后如果是误报的情况再行解封。在做邮件预警时,因为每个玩家每天要做装备道具数量缓存,因此会有一定内存消耗,对在线人数不多的游戏可以考虑使用。(后续会写邮件预警实现的博文,邮件预警还可监控后台异常报错报警)
三、请求间隔控制
有些功能虽然做到安全检查了,确实不会存在刷资源的问题了,比如有个卡牌类的组队副本玩法,一点挑战就计算好战斗并发奖并把战斗流发给前端播放,三个人组好队后每人还有10次组队收益,当一点挑战时,各个安全检查都通过,比如都还有收益次数,都符合挑战需求的等级等等,但是,当网络卡的时候,那个挑战按钮还是可以点击的,这样队长连续点10次以上,那么一下就把10次收益做完了,这不能算是刷资源,因为只可挑战规定的那么多次,而且每次点击时,都符合挑战和发奖要求,只是这样不好,因为本来预计让玩家半个小时的玩法,现在1秒内就玩完了,这样他就可以抽出时间打其他资源了,对别的玩家不公平,所以这个时候,可以加个请求间隔控制的功能,对同一个玩家同一个功能限制多少秒(比如3秒)时间内不可以请求第二次,过了这个时间才可请求,这个时间足以抵挡网络延迟,让前端正常播放战斗流,符合玩法预期目标了。注意的是这样也会有一部分内存消耗,在内存条件允许的情况下可以这么做,下线清掉玩家的各个功能间隔限制缓存。
四、机器人测试
既然资源被刷的明显特点就是客户端的频繁请求,那么我们可以做个功能机器人(后续博文会实例介绍如何做各种功能的压测机器人),让机器人随意组装协议,随意大量请求该功能玩法协议,也有一定几率能测出被刷bug,提前做好预防。
五、治标不如治本
做好预防,最好的方法就是写功能逻辑时,特别是关乎资源产出和消耗的功能时,一定要做好各种安全检查,多想象下客户端作弊攻击的情景,不要相信前端上传的字段,不要相信策划说功能验收OK了,不要相信测试说功能测好没问题了,必须自己至少测一遍,特别是临界点的测试,时刻关注数据表是否按预期更新记录了,自己测好的才最放心,因为线上一旦出事被刷,最大背锅的人是写这个程序的你。