做开发的人,尤其是做客户端(C/S)系统开发的人都会遇到一个头疼的问题,就是软件的自动更新;系统发布后怎样自动的更新程序,在下有幸开发过一个自动更新程序,更新程序与任何宿主程序是完全独立的;只要在主程序里面启动更新程序就行了;更新程序也是一个可执行文件,在启动的时候可以设置是否是自动更新和是否是手动更新,自动更新的意思就是说不需要人工的干预实现从远程服务器下载更新包,而如果是手动更新就会涉及到用户点击程序中的按钮实现更新;在自动更新与手动更新中可以根据项目的需要进行选择,有的程序必须要求用户进行更新才能继续使用,所以程序自动更新是有必要的;手动更新就是用户可以随时更新程序,不需要严格的控制版本问题;下面本人就来讲一下具体的实现细节,我贴出部分代码,源码属公司财产本人不宜上传;
自动更新的目的就是将服务器上的DLL文件拷贝到本地执行目录中,并且覆盖本地同名的文件;流程很简单,但是实现起来有几个地方需要注意:
1.大批量的DLL文件怎么下载到本地来,有多个DLL文件在下载过程中如果网速慢的情况下可能出现丢包、丢文件等情况;本人的实现是将多个文件通过ICSharpCode.SharpZipLib组件进行打包,这样可以省很多事;(如:动态连接库文件dll的名称在传输过程中大小写可能会变化)
2.下载到本地了,怎么覆盖原有的同名文件;本人的实现是先同名的文件的支持删除,然后解压缩;这个过程需要临时保存删除的文件,防止操作失败程序无法启动,要注意有事务性的原理;
3.如果更新的文件不只是单单的DLL文件可能还有一些无限极的文件夹;本人的实现是如果存在同名的文件夹,直接递归的删除,然后将其解压缩到目录中;由于压缩包解压后的顶级目录是压缩文件的名称,所有在复制的过程中需要注意目录的层次关系;
下面我们来走一下实现的整个流程,虽然没有给出整个源码,但是如果看完这篇文章的你基本实现起来没什么大问题了;
为了部署方便我建议大家麻烦点实现一个部署文件的工具,将所有的文件直接打包在里面同时生成服务器端的版本信息文件;
利用这个工具就很方便的实现了对文件进行压缩、生成HASH值、版本文件、更新地址等信息;
这个XML中保存的是服务当前的版本信息、更新文件的名称、更新文件的HASH值,为什么需要HASH就是怕更新文件在某些情况下被人调包了,如果所有的客户端更新后后果很严重;所以我们必须带上HASH值;
工具生成两个文件,一个是版本文件一个是更新包,服务器的任务已经完成,下面就是具体的客户端的实现;
为了知道何时需要进行版本更新所以要在客户端程序目录中保存一份用来记录版本信息的文件;
文件中保存着当前本地的版本号、服务器的更新地址、宿主程序的名称,需要宿主的名称就能在更新的时候将宿主程序重进程中枚举出来然后关掉,这样就不影响我们更新了,当然也可以实现宿主程序不关闭的情况下更新,如果用到某些已经被宿主程序占用的情况会直接影响更新流程,所以以防万一关了为妙;
这是客户端版本文件中保存的信息;
我们上面说了,更新分为手动和自动,我们先来说手动更新吧,手动更新就是需要用户自己去点击更新按钮然后开始更新,这个问题我们可以利用进程的参数传递解决;
当然在更新程序里面需要有这方面的逻辑判断;
入口的地方我们进行判断,更新方式;这里的下载远程更新包是用WebClient对象,也可以用其他的基于Socket的对象;更新开始之前需要先判断本地的版本号是否小于远程版本号,如果小于在进行更新;
因为下载的过程是异步的所以需要用到后台线程建议大家使用System.ComponentModel.BackgroundWorker这个后台线程对象,他对Thread进行了很好的封装;下面来看一下核心的流程代码:
- //开始执行升级
- private void Btn_update_Click(object sender, EventArgs e)
- {
- Btn_update.Enabled = false;
- Btn_close.Enabled = false;
- this.notifyIcon1.Visible = true;
- this.notifyIcon1.ShowBalloonTip(5000);
- //下载远程更新说明xml
- if (DownXml())
- {
- //下载完成后检查是否需要下载更新包
- if (FileWork.IsUpdate())
- {
- if (Util.GetProcessName())
- {
- if (MessageBox.Show("为了更新的顺利完成,请退出应用程序!", "信息提示", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes)
- {
- Util.KillProcess();
- }
- else
- {
- File.Delete(Util.GetDictiory() + "\\ServerUpdateFiles.xml");
- this.Close();
- Back_thread.CancelAsync();//取消线程
- }
- }
- //创建事件日志文件
- LookEventLog();
- Back_thread.RunWorkerAsync();//开始辅助线程
- }
- else
- {
- //无需更新
- MessageBox.Show("您当前是最新版本无需更新!", "信息提示");
- File.Delete(Util.GetDictiory() + "\\ServerUpdateFiles.xml");
- this.Close();
- }
- }
- }
- //关闭
- private void Btn_close_Click(object sender, EventArgs e)
- {
- Application.Exit();
- }
- //开始辅助线程操作
- private void Back_thread_DoWork(object sender, DoWorkEventArgs e)
- {
- try
- {
- //实例化下载对象
- downclient = new WebClient();
- downclient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(downclient_DownloadProgressChanged);
- downclient.DownloadFileCompleted += new AsyncCompletedEventHandler(downclient_DownloadFileCompleted);
- //下载远程更新包down.zip压缩文件|放在应用程序目录下|相应界面事件
- downclient.DownloadFileAsync(new Uri(Util.GetUpdateUrl() + "down.zip"), Util.GetDictiory() + "\\down.zip");
- }
- catch (Exception err) { System.Diagnostics.Debug.WriteLine(err); }
- }
- //在异步下载结束时触发该事件
- void downclient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
- {
- try
- {
- if (e.Error != null)
- {
- eventLog1.WriteEntry(e.Error.ToString());
- MessageBox.Show("在进行远程更新时,发生错误", "信息提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
- }
- else
- {
- Util.KillProcess();//关闭主进程
- //验证哈希值
- if (Util.IsHash(Util.GetHash(Util.GetDictiory() + "\\down.zip"), FileWork.GetDownHash()))
- {
- //删除无用压缩文件
- File.Delete(Util.GetDictiory() + "\\down.zip");
- //删除无用版本文件
- File.Delete(Util.GetDictiory() + "\\ServerUpdateFiles.xml");
- MessageBox.Show("远程服务器更新包已发生变化,无法更新", "信息提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
- eventLog1.WriteEntry("远程服务器中的更新包在制作和下载时间段中数据包发生变化,为了安全期间不给予下载!");
- this.Close();
- }
- else
- {
- //解压压缩包文件
- ReduceToUnReduceFile.unZipFile(Util.GetDictiory() + "\\down.zip", Util.GetDictiory());
- //删除压缩包文件
- File.Delete(Util.GetDictiory() + "\\down.zip");
- //检查文件夹层次结构
- FileWork.LookFiles(Util.GetDictiory() + "\\down", Util.GetDictiory());
- //订阅复制文件事件
- FileWork.CopyFileEvent += new FileWork.CopyFileDelegate(FileWork_CopyFileEvent);
- //递归复制文件
- FileWork.CopyFiles(Util.GetDictiory() + "\\down", Util.GetDictiory());
- //删除临时文件夹
- FileWork.DeleteFiles(Util.GetDictiory() + "\\down");
- //如果库结构更新成功,则才能更新程序的版本号,否则下次继续更新
- if (EventChainReference.GlobalEventChain.OnAutoUpdateDb())
- //更新本地版本号信息
- Util.UpdateLocalXml();
- File.Delete(Util.GetDictiory() + "\\ServerUpdateFiles.xml");
- MessageBox.Show("升级成功!", "信息提示");
- Util.StartProcess();
- isupdate = true;
- }
- }
- }
- catch (Exception err) { eventLog1.WriteEntry(err.ToString()); }
- Application.Exit();
- }
这部分代码是串联整个过程的代码;
自动更新大概就讲完了,几个关键的地方都给出了,希望对大家开发自动更新程序有帮助;
本文转自 王清培 51CTO博客,原文链接:http://blog.51cto.com/wangqingpei557/564795,如需转载请自行联系原作者