在本文中,笔者将以webgame研发者角度,符合游戏营业模块逻辑,从营业需求,数据库设计,法度编写,把持编制上来讲授缝隙构成道理,规避方案,也欢迎大年夜家会商。
登录认证
近几年,网页游戏几近都是以联运编制运营,意味着游戏办事器本身不保留用户暗码,用户登录在平台,经由过程平台跟游戏办事器的接口对接登录。接口做加密认证。故webgame的帐号暗码安然标题问题,这里不提了。但登录认证的hash字符串安然,也仍是要寄望的。好比登录hash字符串的生效时候,hash字符串的加密参数来历,好比包含用户名、登录IP,浏览器user-agent等数据,以避免改hash被泄漏了,也是很难经由过程办事器的验证。
游戏充值
webgame的游戏充值流程,跟通俗网页充值流程一致,没有特别的处所,其不合点就是跟其他浩繁平台做结合运营时,势需要每个公司做接口对接,且接口规范各色各样,且游戏厂商没有话语权,必需遵循他们的接口规范来,这其实毒手。腾讯的充值接口的验证编制,安然性做的较为凸起,大年夜约代码:
// 返回参数列表
$signKey = array('openid','appid','ts','payitem','token','billno','version','zoneid','providetype','amt','payamt_coins','pubacct_payamt_coins');
$sign = array();
//从GET参数中,对比找出上面参数的值
foreach($signKey as $key ) {
if (isset($data[$key]))
{
$sign[$key] = $data[$key]; //只有 GET里有的参数,才介入sig的计较
}
}
######开端生成签名############
//1: URL编码 URI
$url = rawurlencode($url);
//2:遵循key进行字典升序摆列
ksort($sign);
//3: &拼接,并URL编码
$arrQuery = array();
foreach ($sign as $key => $val )
{
$arrQuery[] = $key . '=' . str_replace('-','%2D',$val);
}
$query_string = join('&', $arrQuery);
//4 以POST编制拼接 1、3 和URL
$src = 'GET&'.$url.'&'.rawurlencode($query_string);
// ## 机关密钥
$key = $this->config->get('qq_appkey').'&';
//### 生成签名
$sig = base64_encode(hash_hmac("sha1", $src, strtr($key, '-_', '+/'), true));
if ( $sig != $data['sig'] ) {
$return['ret'] = 4;
$return['msg'] = '要求参数弊端:(sig)';
$this->output->set(json_encode($return));
return ;
}
在此根本上,还可以做的严谨点:
增加随机参数名、参数值。随机参数名、参数值由联运方随机生成,遵循参数名的字符串所属ASCII码挨次排序,参数名、参数值均介入sign的计较,增加暴力破解密钥(app key)难度。
增加回调验证订单号,金额信息。游戏充值办事器领遭到充值要求时,反向到该平台回调接口,确认此笔订单有效性,以避免加密密钥泄漏的标题问题。
长途文件引进
在网页游戏的研发中,大都都是利用框架来做,即便用REQUEST来的参数,作为要求文件名的一部门,来利用,那么很等闲构成长途文件引进的缝隙。在我们之前的游戏中,曾呈现过一例如许的缝隙标题问题。
// Load the local application controller
// Note: The Router class automatically validates the controller path. If this include fails it
// means that the default controller in the Routes.php file is not resolving to something valid.
if ( ! file_exists(APPROOT.'controllers/'.load('Router')->getDirectory().load('Router')->getClass().EXT))
{
load('Errors')->show404('Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.');
}
include(APPROOT.'controllers/'.load('Router')->getDirectory().load('Router')->getClass().EXT);
load('Benchmark')->mark('load_basic_class_time_end');
webgame中的长途文件引进
从代码和案例图中,可以看到对REQUEST的参数没有过滤措置,直接作为文件名来include引进的,故导致这类标题问题,近似上页图中QQ群网站的缝隙。若PHP version <5.3.4 ,还会产生Null(%00) 截断的标题问题,带来更大年夜的安然标题问题。在我们新的项目中,我们更改了实现编制,我们游戏所有接口城市走gateway,gateway里,对节制器名做类名规范的检测措置,再在指定几个目次下做autoload加载文件,且还会对REQUEST的类名、编制用ReflectionClass反射类的措置,检测到类、编制、参数是不是合法。一来避免『长途文件引进』缝隙标题问题,二来便于前后端联调时,抛出更具体的异常,便利调试。下面为参考代码:
require_once CONFIG_PATH . "/auto.php";
spl_autoload_register("__autoload");
……
//默许动静格局
$view->clear();
$view->error(MLanguages::COM__INVALID_REQUST);
$msg = new Afx_Amf_plugins_AcknowledgeMessage($val->data[0]->$messageIdField);
$msg->setBody($view->get());
$message->data = $msg;
…
$a = new Yaf_Request_Simple();
$a->setControllerName($method[0]);
$a->setActionName($method[1]);
$objC = new ReflectionClass($method[0]."Controller");
$arrParamenter = $objC->getMethod($method[1]."Action")->getParameters();
$arrRequest = isset($val->data[0]->body[0]) ? (array)$val->data[0]->body[0] : array();
$bCanCall = true;
foreach ($arrParamenter as $objParam)
{
$parm = $objParam->getName();
$bIsOption = $objParam->isOptional(); //是不是为可选参数
if (isset($arrRequest[$parm]))
{
$a->setParam($parm , $arrRequest[$parm]);
}
elseif ($objParam->isOptional())
{
//可选参数
}
else
{
$bCanCall = false;
}
}
if ($bCanCall)
{
$rp = $app->getDispatcher()->dispatch($a);
$msg = new Afx_Amf_plugins_AcknowledgeMessage($val->data[0]->$messageIdField);
$msg->setBody($view->get());
$message->data = $msg;
}
SQL 注进
SQL注进道理、编制,跟通俗web利用一样,没甚么出格的,在利用REQUEST来的参数时,过滤措置便可。可能在动静格局,和注进把持精练上,会蒙蔽研发人员的眼睛,被忽视掉落了。好比我们项目标AMF动静格局,在前端界面没出来之前,我们后端法度员一般利用Pinta来摹拟把持,调试法度。前端界面出来以后,会利用Charles proxy来捕获http要求。在这些过程中,要求接口、参数的机关,没有通俗web那么简单。研发人员也等闲忽视对要求参数的过滤,故很等闲构成这类标题问题。构成道理见:《WEB开辟安然与运维安然浅见》,防御编制做过滤措置,或SQL预编译。
AMF动静格局的WEBGAME中的SQL注进
AMF动静格局的WEBGAME中的SQL注进
为了进步游戏办事器的吞吐能力,网页游戏的架构也是一向在演变的。在之前以Mysql作为数据存储的webgame架构中,其他节点都是可以程度扩大,或说依托简单粗莽的增加办事器来解决,单单作为独一数据存储中间,不克不及这么做。为此,良多webgame的数据存储改用Nosql来代替,乃至java、C/C++的游戏数据,直接在内存中把持,游戏关服时,才写进到DB中。故SQL注进的标题问题,也会愈来愈少。
通信和谈与动静格局
网页游戏当然名字叫网页游戏,但通信和谈并不是满是http,也有良多利用socket,和http+socket并用的做法。我们是http和谈+amf动静格局,和socket并用来实现。在http与https的弃取上,我们考虑到ssl的启用后,大年夜量的ssl解密加密运算,必将会增加办事器大年夜量的CPU计较压力。而传输的内容,大都是游戏营业的把持,响应,是能接管被监听嗅探的行动的(认证信息除外)。站在安然角度,这不克不及理解。但站在产品角度,考虑一下 投进产出,然后选择http通信,也是可以理解的。socket在我们游戏中,除在聊天利用上利用外,在一些组队、帮派战之类需要多个玩家之间同步数据信息时,我们也会利用socket来推送数据。在利用socket作为所有营业传输的和谈时,和谈格局一般都是开源和谈,好比msgpack、protobuf之类,或自定义的和谈。利用自定义和谈时,务必检测全部动静包的每个参数,类型范围,避免个别超大年夜数值、鸿沟数值呈现,导致主法度内存越界,乃至于办事宕机,没法正常办事的环境产生。
金币复制-整型溢出
上周周六开周会时,听到其他项目组的一个关于整型溢出导致产生刷金币的标题问题。在这里,我抽象该案例,分享一下。商城出售开启背包格子的所需道具『梧桐木』。在游戏中,用户包裹格子数量一般城市作为一个收费点,一款游戏的格子大年夜约为每行7格子,一共8行如许。好比前面3行是默许开放的,第4行是收费的,并且第一个格子所需品梧桐木的代价1个银子,第二个梧桐木是2个银子,第三个是4个银子。顺次类推,意味着这些梧桐木的代价总和其实就是一个第一项为1,公比为2,项为35的等比数列。 当用户选择采办梧桐木数量大年夜于31时,好比32-36中这些数字时,这些等比数列的和就是大年夜于2147483647。(只是举例,实际上不会以如许的代价出售物品)
在java中,4字节的存放int型变量的范围是-2147483648至2147483647。在java、c的有符号int型中存储时,数的最高位描述符号位,4字节共32位,除往最高位的符号位,剩下31位,每个位上能暗示2个数字,4字节的有符号的整数暗示范围为:负整数2^31个,范围为『-1至-2147483648』;正整数2^31个,范围为『2147483647至1』。 好比下图(寄望十进制数字跟二进制暗示的改变挨次):
当开启格子数字为大年夜于31时,好比32,那么所需费用就是2147483647个银两,再买点其他物品,凑成超越2147483647的数字,好比又买了3个银子的其他道具,总共破钞2147483650个银子,在4字节的有符号int中暗示出来的成果,变成符号位为1,即负整数。数值位为0000000 00000000 00000000 00000010,也就是10000000 00000000 00000000 00000010,对应十进制的-2147483646。法度逻辑上,再鉴定现有银两是不是足够付出此笔破钞时,是经由过程的。当利用当前余额减往这笔破钞时,将变成减往一个负数,那么实际上就是加上一个正整数。变成了本身银两账户余额的增加。而余额字段类型是long,则准确的存储了这些余额,溢出漏洞被操纵。在C中,利用无符号的数值类型,便可完成数值类型溢出刷钱的行动,但在java中,仿佛没有没有符号的类型。这也能够先肯定所有介入计较的数值必需为正整数作为需要前提(游戏营业特点,游戏内所稀有字,必定全为正整数,乃至都不包含零),先做大年夜小鉴定,再做正正相加,不克不及得负;负负相加,不克不及得正。来鉴定是不是产生了溢出标题问题。在PHP中,不消担忧溢出标题问题。
金币复制-并发要求
Rpg类型的网页游戏中,大都都有道具出售的功能,直接卖到商铺,和道具材料从商铺买进功能。当玩家同时针对买进、卖出两个把持,刹时大年夜量并发要求时,在办事器的措置逻辑一般有分别的两个过程措置,共享数据别离数据库中的对应账户余额表,以下图:
webgame买进、卖出并发要求措置
//卖出
// startTrans
$iBalance = $obj->getBalance('user1'); //余额50
//UPDATE `role_gold` SET gold = 150 WHERE role_id = 1
if(!$obj->setBalance('user1',$iBalance + 100))
{
//rollback
}
//扣除物品
if (!$obj->delItems($items))
{
//rollback
}
//commit
//买进
// startTrans
$iBalance = $obj->getBalance('user1'); //余额50
//UPDATE `role_gold` SET gold = 0 WHERE role_id = 1
if(!$obj->setBalance('user1',$iBalance - 50))
{
//rollback
}
//发放物品
if (!$obj->addItems($items))
{
//rollback
}
//commit
卖出要求的措置过程为1,买进要求的措置过程为2。在过程1还没将成果写进到DB时,过程2也从DB读取到余额为50。这是,两个过程拿到的余额信息都是50。过程1遵循逻辑代码,计较出残剩余额是150;过程2计较出的残剩余额是0。最后,不管阿谁过程最后写进余额,都是弊端的成果。(注:这里的代码逻辑把持,跟mysql事务无任何干系,事务只能包管单个过程的事务范围内多条语句都准确履行,或回滚。好比能包管扣钱成功,且物品删除掉落的两个语句都准确履行。能包管此中之一的语句履行掉败时,都准确回滚。)
其实,在事物开启时辰,SELECT语句是不是可以取到最新的数据,或是不是需要等候锁释放,取决于MYSQL的事务隔离级别。在MYSQL的事务隔离级别中,有一下几种隔离级别:
READ-UNCOMMITTED(读取未提交内容)级别
READ-COMMITTED(读取提交内容
REPEATABLE-READ(可重读)
SERIERLIZED(可串行化)
对READ-UNCOMMITTED,可以读取其他事务中未提交的数据,并且传闻机能还高不到哪里往,几近没有在实际利用中利用;对READ-COMMITTED,在统一事务中,会因为其他事务随时可能有新的commit,导致统一select可能返回不合成果。这个也不合适游戏营业;再说第四个SERIERLIZED,只要事务开启,所有其他查询,均列队等候该事务提交以后,对上面提到的卖出买进环境,第二个事务的SELECT把持,不会当即返回,会处于锁等候状况,一向到前一个事务结束。这个隔离级别,当然能避免上面的标题问题,但机能较差,一般不会往利用。而REPEATABLE-READ隔离级别,也是mysql默许的隔离级别,从功能上,比较合适游戏营业需要,也应当是广大年夜webgame架构中mysql的默许隔离级别。
对这个标题问题,你可能很快就给出解决编制,把UPDATE语句改成UPDATE `role_gold` SET gold = gold + 100 WHERE role_id = 1或UPDATE `role_gold` SET gold = 150 WHERE role_id = 1 AND gold = 100来解决,但这类多个事务同时把持点窜多个表的多笔记及时,还等闲激发死锁标题问题,好比《webgame中Mysql Deadlock ERROR 1213 (40001)弊端的排查过程》。并且,当前提为跨表内数据是不是存在,或别的前提不在MYSQL中,而在其他收集接口的响应中时,若何做呢?
金币复制--逻辑缝隙
援引DNF的缝隙新闻 《操纵网游缝隙狂刷游戏币赚钱 玩家自曝3天赚17万》
玩家曝出刷币缝隙 一个游戏道具可刷400人平易近币
该缝隙事实是甚么?本来游戏中“云幂袖珍罐”这个道具,可以开出2件一样的游戏设备,还有极少概率开出游戏币,开出的设备不值钱,但假定开出金币了,则分为5000万、8000万和1亿游戏币。而1亿游戏币,按正常市场行情,可在生意网上卖400多元人平易近币。据玩家称,在游戏中,角色的设备是需要用包裹来存放的,不外今朝角色的包裹最多只有48格,也就是只能存放最多48件设备。缝隙就是操纵包裹的有限空间,存放47件设备(存放满了又没法开罐子),只留下一格空位,而在开“云幂袖珍罐”出设备时,就会因包裹空间不足,而导致开罐掉败,而罐子还存在。玩家继续开罐,直到呈现金币,但金币不会据有包裹的空间,是以开罐成功,然后罐子消掉。发现这个缝隙后,部门玩家狂刷游戏币,然后顿时在第三方生意平台出售游戏币,兑换成现金。
这类标题问题,都是研发人员逻辑不严谨导致,这类标题问题,也较难发现。规避编制可以依托下面提到的『运营数据监控』。
道具复制--背包清算
跟上面的卖出、买进一样,同时穿设备、清算包裹。在设计时,可能会将身上设备设计在设备表中;将不在身上的设备,设计到背包表中。当同时进行穿设备跟清算包裹的要求并发时,也会产生跟上面卖出,买进的环境,线程1读取DB,发现包裹里有这设备,然后预备删除背包表的这笔记实,当预备写进到设备表时,别的一个清算包裹要求的线程来了,读取了全部背包表,进行道具的归并、排序。这时候,之前的线程将这个设备写进到设备表,并删除背包表里的数据,并提交事务。这个穿设备的所有把持都是合理、正常,且准确履行的。但别的一个清算背包的线程读取了之前的背包表里的数据,包含那件被穿上的设备。在游戏中,清算背包需要对可堆叠道具做堆叠把持的,意味着需要归并多个道具,删除部门道具。这意味着这里的把持,当前cgi线程的内存中的数据,将城市以笼盖的情势,写进到DB中,那么意味着,之前被穿到身上的那件设备,也会从头被写进到背包中。那就变成两张表里呈现了两个不异独一ID的不异属性的道具。玩家便可以把背包中的这个道具出售给其他玩家。
在java或C之类法度中,数据放内存中的游戏,也会存在这个标题问题,除非做读锁,但读锁会带来锁等候,锁等候会导致线程被占用,梗阻后面要求的措置,聚积大年夜量要求。导致系统负载升高,办事器繁忙,乃至于没法响应。好了,大年夜约理解道具复制的构成启事了吗?这个标题问题,我们从根来历根底因想想,标题问题到底呈此刻哪里?若何规避呢?细心的同窗不难发现,对穿设备的把持成果,会对下一个要求产生影响的,当前把持未获得办事端响应之前,办事端是不克不及措置下一个响应的。对此,我们做了响应措置锁--『用户并发要求锁』。
用户并发要求锁的实现,php中session以文件情势存储时,php会对session文件加锁,不释放(假定不特意履行session_write_close),知道当前响应完成。别的一个线程才可以正常读取,这简介的构成了单个用户的并发要求锁,可是,后面的过程一向处于等候状况,也会占用一个php-fpm过程,梗阻其他用户的正常要求对php线程的利用。为此,我们利用NOSQL的K-V情势布局,以user_name为key的情势,实现用户并发要求锁,第一个要求,生成这个k-v数据,后一个要求发现有这个key了,那么当即抛出异常,结束响应,FLASH按照异常内容,提示用户不要进行歹意把持。即不会产生并发要求,又不会梗阻要求措置。同时,在要求结束的析构函数里,对这个锁进行删除把持,不影响下一个正常要求。若因为法度异常,产生语法弊端,导致析构函数没法履行,没有删除用户锁时,可以在生成锁的时辰,设置过不时候,好比5秒,乃至2秒,操纵nosql的过机会制,实现用户解锁,避免用户长时候没法正常游戏。
类CC报复打击-多用户共享资本锁的timebomb
我们此刻研发的项目,是以NOSQL Redis作为DB,来存储数据的,redis并没有成熟的事务措置机制,watch乃至算不上关系型数据库中的事务措置。对此,更需要对表进行加锁解锁。java之类说话的项目,良多都是直接把持内存的,更是需要资本锁,来解决并发标题问题,解决多个要求把持统一份数据的标题问题。公司有别的一个项目,呈现过一次因为锁的颗粒度较大年夜,带来的锁等候timebomb的标题问题,也导致了线程繁梗阻忙,要求聚积,系统负载上升,导致宕机的标题问题。这个项目标锁是针对所有效户的锁,每个用户的要求发来时,当火线程会对所有效户的数据加锁,直到响应完成,才释放掉落。这么做,是为体味决因当前把持,会影响到其他用户数据,好比多人PK,多个玩家之间的交互。
当其他要求一并发来时,那么资本会当即被锁住,直到上一个要求结束,才释放锁,那么其他线程都处于等候状况。用户基数小时,是看不出来锁带来的影响的,内存把持都比较快。当用户基数大年夜时,或说要求数增大年夜时,后面的要求的等候时候会愈来愈长,超越webserver的等候时候,直接返回timeout,不克不及正常供给办事。
这类标题问题标产生,是因为锁的颗粒太大年夜了,不该该将所有效户都锁住,最好细化到当前要求所影响到的单个用户,只锁住单个用户的数据。如许,才削减timebomb的产生。
其他
知乎里的伴侣提到,良多webgame 的前端做了鉴定,而后端没做鉴定的标题问题,这类标题问题,实属不该存在。在我们的项目中,后端做的验证鉴定,远比前端多的多。有时辰,为了界面上的动画表示,前端flash一般会在用户把持以后,当即衬着,然后,再按照后端响应,决定是不是继续做界面元素改动。好比脱设备,玩家把持时,会先衬着设备从角色面板,跳到背包里的动画,然后,再按照后端响应成果,决定是不是反转展动弹画。如许,避免显得把持后,一按时候的反应痴钝假象,以进步用户体验。当然,后端是必然会做鉴定的,鉴定角色背包是不是有空格之类。此刻的webgame研发,一般都不会存在前端鉴定,而后端不鉴定的做法了。假定有,也应当是个别漏掉环境。
好比往年的time33算法的hash dos的标题问题,利用json动静格局的webgame必然要寄望,php只是在领受要求时,做了最大年夜数量标限制。但在json解码以后的数据中,是没有措置的。这里千万别健忘了。
运营数据异常监控
再完美的防御办法,都仍会有安然缝隙。恰当的监控办法,也必然要有,监控等第、金币、游戏币、经验、珍贵物品的改变等等,一旦发现,当即报警,在缝隙未分散之前,第一时候往修复缝隙,以削减损掉,保护游戏均衡。
日记系统
日记系统必然不克不及漏掉,所有把持,必需写进日记,当安然事务产生后,可以作为各类数据回滚,生意胶葛措置的靠得住数据。也是作为数据监控的最准确的数据来历。
假定你用了我画的小清爽般的插图,请记得为图片写上签名来历,画图是最破钞我时候的一件事。