Home > Article > Backend Development > What are incremental updates to software?
因为转战C#了,之前很多东西都丢了。现在从头开始弄基础服务,首先第一个就是客户端的自动更新。之前简单搜了一下相关功能的实现。有一个文章我没有看懂,另一片文章里边说的应该是提交本地数据,然后计算差异化包,让服务器返回差异化数据包。当然这样不是不行。肯定是可行的,但是对于服务器来说这部分工作可能就有点麻烦了。因为你得让服务器有这个计算能力。参考Cocos2dx 3.9的Lua增量更新模块,简单做了一个基础框架模型出来。
这个其实很简单,就是重新下载一个完整的安装包,然后重新安装一遍,不管原来存不存在内容,如果原来存在内容那么久替换掉,如果原来不存在内容那么就添加上新内容就是了。其实这个说起来很简单,但是可能会存在一些问题。
- 流量问题 可能现在看来这个问题并不是多么大的问题,因为现在带宽已经非常宽了。100M的内容按照10Mpbs的带宽来算,也就一分多钟就能下载完了。 - 渣子问题 这种覆盖安装一般会存在一个渣子的问题。比如说,我在安装目录里边生成了一个不在后续安装包的文件,那么这个文件就没有办法被清理掉。这就可能很尴尬了,比如说你的项目依赖系统提供的一个Dll,如果你的目录中直接存在这个Dll那么就会优先使用你对应目录中的Dll(如果我没记错应该是这样),如果是我作为攻击者的话,我很有可能会给你放一个我种下病毒的Dll。这就很尴尬了
这个原理也比较简单,其实就是我们都觉得完全安装太费劲了,那么我的软件又需要比较频繁的更新,比如说某些桌游可能过个节日要上节日相关的功能,这样就可以添加新的Dll然后又不能出一个一个的完整安装包。那么我可以在完整安装包的基础上打补丁嘛。比如说,我出了版本1.0,过了十天半个月过端午了,我出个龙舟皮肤一类的,那我就可以直接在1.0的基础上打个龙舟补丁,这样他就变成了最新的客户端1.1。如果将来要上别的功能了我就在1.1的基础上打个补丁,让客户端变成1.2。不过这样也会有他的问题。
- 顺序安装 在安装的过程中只能以此递增式安装,我只能1.0 => 1.1 => 1.2;不能1.0 =》 1.2。因为中间是没有对应的补丁的。 - 流量问题 其实这种解决方案可能会带来一些问题,比如说,现在端午节,我需要把房子装饰成龙舟的样式;然后五一劳动节,我又需要把房子装修成五一劳动节的样子。那么都是关于房子的皮肤,我是没有办法都保留的,因为来年的时候肯定就不能这么装修了,因为过时了太Low了。那么关于这部分的内容,如果你想一点一点的升级上来对于最后的版本来说是没用的,你占用的流量一点用都没有。太尴尬了。 - 维护的复杂度 因为你不能直接出了一个1.0之后全都是使用补丁,如果当你的版本号递增到一定程度以后,补丁的大小可能远远超过了你重新去下载一个最新的客户端的大小。所以只能通过时间也好(比如半年或者一个季度)通过意义(比如说大版本号 2.0 3.0)来生成一个完整的客户端。这样用户在下载的时候就可以找一个最近的完整的客户端版本号。然后再打补丁的方式来获得最新的客户端,不过这种维护的复杂度应该也不小。
其实我们的需求很简单,获取最新的客户端。然后附加要求就是要省流量、下载方便、服务端发布方便。
其实说到省流量,就是能用本地的就直接使用本地。本地实在是没有的文件,那么就从网络上下载。这样基本上就做到了省流量的效果。
不需要做太多的操作,当然这个很多软件都做到了这一点。上文中提到的其实也可以做到自动化,比如说,完整安装的那么我就直接下载最新的完整安装包就好了,如果是打补丁的这种,那么就下载最新的完整安装包以及后边的补丁就好了。其实这个真的要做,对用户来说应该是没有感觉得。都一样,不过对于程序员来说。可能面临的开发就不太一样了。
其实这个完全是针对于程序员的了,一般来说,如果这个事情可以程序来自动完成那么就肯定交给程序了。比如说完整晚装包的这种,肯定能够做到自动打包。打补丁的这种,无非也就是根据上一个版本生成一个补丁。或者再生成一个完整安装包。上传到合适的文件服务器就好了。其实打补丁也好,完整安装包也好,都有一个显著的优势就是可以很方便的放到多个服务器上来进行文件的负载均衡。
在考虑这个问题的时候,我想到了之前接触的Cocos2dx 3.9 Lua 自动更新模块,他是这么做,通过一个配置文件,来说明最新的客户端中都包含了那些文件,这些文件的MD5值是什么,然后网络路径是什么。这样客户端拿到这个配置清单的时候,就可以轻松的判断本地的那些文件是可以继续用的。那些文件是过时了的,这样客户端通过配置心中的网络路径位置获取最新的对应文件就好了嘛。不过那也是很久之前的事情了,不然,我就不需要自己重新规划了。直接抄一份代码就好了嘛。还是自己整理一套吧。这样来的更彻底一些,想改什么就改什么。
{"VersionsCheckCode": "XC09VU4QCRD43LRF01BYOD26D45DWEEKX5KECUKIA7Q4160FKAWQBHXTKE63Z148","TimeStamp": 1496649771,"ServerUrl": "http://or2dwwrsz.bkt.clouddn.com","FileInfos": [{"FilePath": "JumpKick.HttpLib\\packages\\Moq.4.2.1409.1722\\lib\\net40\\Moq.xml","FileMD5": "c7e9c70a19b84f31e51eb65f4ee38803","FileUrl": "LV4ZBB_c7e9c70a19b84f31e51eb65f4ee38803"},{"FilePath": "JumpKick.HttpLib\\packages\\Moq.4.2.1409.1722\\lib\\sl4\\Moq.Silverlight.dll","FileMD5": "0ee20e7ccba7d6667c48efebe41503ff","FileUrl": "X057QT_0ee20e7ccba7d6667c48efebe41503ff"},{"FilePath": "JumpKick.HttpLib\\packages\\Moq.4.2.1409.1722\\lib\\sl4\\Moq.Silverlight.xml","FileMD5": "c25417228db2dd820f45e93112e8596c","FileUrl": "S0LO6G_c25417228db2dd820f45e93112e8596c"}]}
VersionsCheckCode:当前的版本文件校验信息。
TimeStamp:做这个文件的的时间戳
ServerUrl:服务器的地址,主要是用来跟后续的文件进行拼接来用的
FileInfos:对应的文件信息列表
FileInfos[?]:FilePath:本地文件路径,从网络下载之后对应的本地地址
FileInfos[?]:FileMD5:这个文件的MD5值,用来判断原始的对应位置的文件是否与网络中的文件相同
FileInfos[?]:FileUrl:这个文件在网络中存在的位置,当然这个是没有前边的URL路径的是ServerUrl后边的内容
其实嘛整个项目最复杂的地方时这个更新的想法与这个文件的制定。剩下的内容其实就比较简单了,就是具体的代码的实现了。代码方便我就懒得讲了,直接把项目的地址扔上来了事。
省流量、跟其他软件结合方便、服务器发布方便。省流量这个上边提到了我就说了。
其实很容易理解,就是这个软件跟被更新的软件一毛钱关系没有。所以我可以直接跑起来就行了,不需要关系具体被更新的软件是怎么搞得。最多采用这个的项目。重新改一下我们这边的UI就行了。
其实最麻烦的事情就是服务器这边。需要生成这个配置文件,我这边服务器端其实并没有在运行指的就是生成这个文件的工具。我可以指定一个目录。然后生成这个文件,将对应目录的所有文件导出到一个输出目录。不过对于很多CDN不支持多级目录(比如七牛),所以我将所有的文件都换掉了名字,让他们尽量的不重复,程序可读就行了。
首先使用我写好的服务端生成对应的配置文件和改名文件。
生成的目录结构是这样色的,配置文件放到一个固定的目录里边去。UpLoad文件夹上传到某一个文件服务器上,这里我是用的七牛云
然后把UpLoad目录中的文件全都上传上来
上传完了就是这个样子的。
致辞服务器就部署好了,等有了新版本重复一遍这个操作就行。其实上传服务器的这部分工作可以集成到服务端中。上传内容就好了嘛,其实很简单的。当然了,这个我懒。之前也没有研究七牛的SDK这个可以作为一个功能上的扩展,反正项目我已经开源了,感兴趣的人可以自己扩展这部分功能。好吧我们继续来说客户端怎么弄吧。
AutoUpdateHelper helper = new AutoUpdateHelper(); helper.WebXmlUrl = "http://7xs9hw.com1.z0.glb.clouddn.com/VersionInfo.json"; helper.ConfigXmlPath = "SynchronizeVersions.xml"; helper.TempXmlPath = "SynchronizeVersions_Temp.xml"; helper.FilePath = "Client"; helper.CallBack = obj => { if (obj is Dictionary<UpdateDataType, object>) { var dic = obj as Dictionary<UpdateDataType, object>; foreach (var item in dic) { if (Name2Action.ContainsKey(item.Key)) Name2Action[item.Key](item.Value); } } }; try { helper.Start(); } catch (Exception ex) { MessageBox.Show(ex.Message); }
其实就是一个简单的设置网址跟配置文件。其实呢这个地方应该吧配置也放到配置文件里边去,为什么没放呢?因为我懒,哈哈哈。
客户端跑完了就关了,其实应该是跑完了运行某一个具体的文件,然后自动更新的逻辑就完成了,这部分我会之后继续完善。
这么样处理其实下载会变得很灵活。但是也会带来其他的问题。比如之前提到的打包的问题。因为服务器只是一个文件服务器,所以服务器并没有计算出差异包的能力,所以所有的文件都是一个一个的下载的,这样就会出现很多小文件的下载。这样的下载其实是比较蛋疼的。这是设计上的坑。为了灵活只能妥协了。
其实到目前为止我只是实现了最基础的功能。甚至还不全,比如之后的文件启动,不过大体的框架已经搭建起来了。至于后边有很多实现不是很合理的地方,我先简单列一列,方便之后维护。
下载失败重试不存在
下载数量的限制不存在(现在是有多少下载多少,很多同时下载可能会存在超时的问题。)
完成了之后没有启动对应的文件
现在没有快速启动的功能(现在每次启动都需要重新校验所有文件,其实可以避免这个问题的。)
Although this project is only an imperfect framework. But this approach should make updates more interesting. Let’s complete this framework together. I prefer to build a successful open source project without forgetting the original intention.
The above is the detailed content of What are incremental updates to software?. For more information, please follow other related articles on the PHP Chinese website!