其实打算做游戏内热更新也是几个月之前的事情了,在方案经历了数次变迁之后,最近才终于应用到了外网的bugfix中。
但是就目前数据来看,热更新由于要下载资源,会使新用户的进入门槛变高,所以留存收到了一定影响,基本降低了10个点。
当然,也可能是热更新的功能存在bug。

好了,我们还是进入正题吧!

方案一

最早的时候,我们想用一种类似打patch的方式来更新。
即将lua代码和资源打成一个zip包,而每个zip包只要代码或者资源发生变化,都会有一个自增的小版本号(大版本号为打在apk或者ipa里的版本号)。
不同的zip包之间diff就可以生成一个patch文件,而为了开发的简单,这个patch列表只会将文件路径列出来,之后客户端要去完整的下载新的文件覆盖。

然而这个方案有很多的问题。

  1. 小版本号不好维护
    因为代码和资源会不停的修改,而如果人工来维护小版本号,会极其复杂。如果通过机器来维护也是个很麻烦的工程

  2. patch包太多
    如果我们将小版本号的patch包先生成好放到服务器上,那么如果我们有100个小版本,就会有非常多 1->100, 2->100, 3->100 … 这种格式的patch包。
    并且如果小版本号继续增长,patch包的增长基本会失控。

  3. 消耗大量流量,速度缓慢
    也许有同学会说,不如只有 1->2, 2->3, 3->4 这样的单个小版本之间的升级patch,客户端自己挨个下载就好。

    但是这样客户端如果是个很小的版本号,就不会不停的重复下载,导致迟迟进入不了游戏,并且流量也大量消耗。

综上所述,方案一有太多的弊端。
这也是为什么我们做完了之后迟迟不敢上线的原因,那么我们来看看方案二。

方案二

方案二相对就简单了很多。
说白了,我们希望有一种方式能够快速的判断出客户端的代码资源,与服务器端的代码资源是否完全一致。
怎么做呢?对的,md5.

依托于我们目前的打包系统,我们现在每个渠道打出来的apk里面的代码资源都是不尽相同的。而我在其打包之后,自动将所有的代码资源,进行了一次md5,生成了一个如下格式的文件md5.txt:

第一列为文件的md5值,用来判断文件是否有变化。
第二列为文件路径,拼接上服务器返回的url前缀即为完整的下载地址。因为我们使用cdn存储文件,所以url前缀随时可能变化。
第三列为文件大小。当提示用户更新的时候,可以算出看到更新的总大小,并在下载中显示文件进度。

那么我们怎么判断客户端的代码资源和服务器是否完全一致呢?
还是md5.
我们只需要判断在相同渠道和大版本的情况下,客户端md5.txt的md5值与服务器的是否相等即可。

为了节省流量,客户端每次进入游戏的时候会将本地的md5.txt的md5值发给服务器,服务器在判断之后告知客户端更新状态是无需更新、建议更新、还是强制更新。
如果不是无需更新,就要把服务器端md5.txt的下载路径返回给客户端,客户端下载下来之后,将两个md5.txt对比后列出不同的文件列表,并计算需要下载的总大小。之后提示用户更新或者直接进入更新逻辑。

大概的说明就是这样,我们接下来说下服务器端和客户端代码的具体实现。

服务器

我们后台是使用django的,所以天生带了一个管理后台,在models中添加我们自己的逻辑也比较简单。
大家如果是别的框架,其实原理也是一样的。

数据库表:

网络参数:

客户端

客户端的代码逻辑要稍微复杂一些:

  1. 在cpp层,创建一个update目录,并将lua的search优先级调到最前面。
    这里我们很不幸的踩过一个坑,就是我们把这个update目录的名字固定为update。但这样会带来一个问题,就是当用户更新了大版本之后,还是会优先去读取这个update目录的代码,从而导致进入不了游戏。
    正确的方式应该是将这个将渠道和大版本号也拼上,比如 update_CN_IOS_APP_91。
    ios和android的具体代码如下:

  2. 当与服务器进行网络交互后,服务器告知客户端需要更新时。
    先创建一个以新md5值为名的目录,并将新的md5.txt下载其中,之后将所有不同的文件下载到这个目录里。
    当所有文件下载完成后,将新的md5.txt文件拷贝update目录,之后重新进入游戏。

由于lua操作文件系统的限制,我们需要将一个lfs的库编译近来,具体可以去cocos2d-quick中找。

另外要注意,当更新完毕重新进入游戏时,需要将所有引入的lua代码删除再重新导入。
而如果热更新的代码本身被修改了的话,则需要将热更新代码本身也重新导入。并重新走一遍热更新判断逻辑。

基本就是这样子了,很多细节代码不方便放出,大家自己想想应该写起来也不难。

游戏服务器端架构升级之路-V3重构

一. 前言 2016年6月17日凌晨5点钟,我们完成了服务器端V3版本的重构,切换的过程十分平滑且没有对线上用户产生任何影响。 这也正式标志着,我们的游戏服务器...

阅读全文

游戏服务器端架构升级之路

这几天的心情非常好,主要原因是我们把服务器端的架构升级到了 2.0,这样最大的一个好处就是: Server重启完全不会影响外网服务 所以,也是想趁此机会,服务...

阅读全文

一个VC桌面游戏作品

这两天整理电脑里一堆乱七八糟的东西,无意中翻出了本科时和室友一起做的一个小游戏,重温了一下,发现还是蛮有意思的,决定把它写下来,算是一篇开发回顾吧...

阅读全文

  1. 我之前做的cocos2d-js的热更新方案也是差不多的方式。

    游戏启动的时候卡出加载检查更新确实不大好,会增加启动时间。我们的做法是先进游戏,后台检查更新,如果发现更新就弹一个窗出来说有更新,更新完成后重启游戏。

    另一个问题是,更新文件列表可能会很大,每次启动都要下载不大好,可以加一个版本号文件,每次更新完把版本号写到本地,启动的时候先从服务器读一个版本号,如果版本号一致就什么都不干了。这个版本号我们一开始是用一个自增id,多人开发经常冲突,后来换成了更新生成的unix timestamp。

    [回复]

    朱念洋 回复:

    嗯啊,我们也不是每次都下载,文中有写,是比较这两个列表文件的md5值

    [回复]

  2. 我把对比资源列表的逻辑放到了客户端,没有服务器逻辑,服务器上只放两个静态文件一个版本号、一个资源列表,和更新资源部署到一起,做CDN

    [回复]

  3. 我们以前也是这么干的,另外patch的手法也还是有,大版本直接下服务器准备好的patch包了,小版本就这么直接下文件~

    [回复]