zoukankan      html  css  js  c++  java
  • 简单的自动更新程序实现

    image 本文将演示一种桌面程序自动更新方案,其步骤比较多,但原理非常简单,通用性尚可,对于小型应用来说,直接拿去就可以用了。

    原理

    服务器端的结构是这样的:

    image

    其工作原理如下:

    Update.asmx仅提供一个功能,就是检测是否需要更新,在需要更新的时候就返回一个更新地址,通常情况下返回的地址就是Download.ashx,而在某些特殊情况下,也可以修改服务端使之从其他Url提供更新下载。检测是否需要更新的的具体做法是:首先获取Updata目录中的主程序版本号,再获取数据库中的最新版本号,两者对比。如果相同则直接与客户端提供的版本号相对比并返回结果;如果不同则将主程序版本号写入数据库,然后生成新的更新文件包,直接向客户端返回更新地址。

    Download.ashx的功能仅仅是将最新版本更新文件包输出。

    而客户端部分包含主程序、Update.exe以及其他附属文件,更新时由主程序检测并下载更新,在主程序退出时,如有更新并已成功下载,则调用Update.exe完成解包及更新覆盖工作。需注意的是:Update.exe永远不能被更新,因为它无法更新其自身,所以服务端更新时也不要将Update.exe纳入更新包。

    下面就是来实际编写一个自动更新解决方案:

    服务器端

    首先建立一个Web服务项目,项目名为“自动更新服务”:

    image

    添加一数据库,名为Database.mdf:

    image

    在数据库中创建新的数据库关系图,并如下设计数据库表:

    image

    创建一个Ado.Net Entity Data Model,名为Model.edmx:

    image

    从刚才的建立的数据库中生成模型:

    image

    在Web.Config的appSettings节点中新增两个节点,用以设置更新程序的主文件名及更新包下载地址:

    <appSettings>
        <add key="主程序文件名" value="MyApp.exe"/>
        <add key="更新包下载地址" value="Download.ashx"/>
    </appSettings>

    引入一个GZip类用以打包(该类的源码将在文章末尾随本文示例源代码一并提供):

    image

    添加一个新的Web服务,名为Update.asmx:

    image

    书写如下代码:

    [WebMethod]
    public string GetUpdate(string ClientVerison)
    {
        if (获取最新版本() != ClientVerison)
        {
            return System.Web.Configuration.WebConfigurationManager.AppSettings["更新包下载地址"];
        }
        return null;
    }
    
    static string 获取最新版本()
    {
        string v = 获取文件版本(HttpContext.Current.Server.MapPath(string.Format("~/App_Data/Update/{0}", System.Web.Configuration.WebConfigurationManager.AppSettings["主程序文件名"])));
        using (var c = new DatabaseEntities())
        {
            //从数据库取得最新版本信息
            var q = c.UpdateVersion.OrderByDescending(f => f.PublicTime).FirstOrDefault();
            if (q == null || v != q.Version)
            {
                //数据库中的版本与当前主程序版本不一致时,以主程序版本为准,写入数据库,并生成新的更新文件包
                var d = new UpdateVersion() { Version = v, PublicTime = DateTime.Now };
                c.AddToUpdateVersion(d);
                c.SaveChanges();
                打包更新文件(HttpContext.Current.Server.MapPath("~/App_Data/Update/"), HttpContext.Current.Server.MapPath("~/App_Data/Update.gzip"));
            }
        }
        return v;
    }
    
    public static void 打包更新文件(string 打包目录, string 输出文件)
    {
        GZip.压缩(输出文件, Directory.GetFiles(打包目录).Concat(Directory.GetDirectories(打包目录)).ToArray());
    }
    
    public static string 获取文件版本(string 文件路径)
    {
        FileVersionInfo f = FileVersionInfo.GetVersionInfo(文件路径);
        return f.FileVersion;
    }
    创建Download.ashx,用以输出更新文件包:

    image

    代码:

    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "application/zip";
        context.Response.WriteFile(context.Server.MapPath("~/App_Data/Update.gzip"));
    }

    服务端至此就编写完毕了。

    客户端

    新建一个WinForm应用程序项目,名为Update:

    image

    建好之后直接删掉Form1.cs吧,此程序不需要界面,在Program.cs中写代码就可以了。

    同样需要引入GZip类用于解包:

    image

    然后编写代码:

    [STAThread]
    static void Main()
    {
        try
        {
            var d = DateTime.Now;
            while (DateTime.Now.Subtract(d).TotalSeconds < 10) Application.DoEvents();
            GZip.解压缩(Path.Combine(Application.StartupPath, "update.data"), Application.StartupPath);
        }
        catch { }
    }

    这里的作用就是等待10秒,然后解包update.data文件,覆盖到当前目录中。

    现在来建立主程序,主程序是WinForm、命令行、WPF都可以,我们新建一个WPF应用程序,命名为MyAPP:

    image

    为程序添加服务引用:

    image

    这里的地址使用的是本地的调试地址。

    为了检测主程序自身的版本号,还需要添加对System.Windows.Forms的引用。

    然后开始设计界面,这里仅为演示更新操作,所以界面上只是简单的设计了更新相关的提示、操作控件:

    image

    代码为:

    <Window x:Class="MyApp.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="377" Loaded="Window_Loaded" Closed="Window_Closed">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
                <RowDefinition Height="1*" />
            </Grid.RowDefinitions>
            <Label Margin="0" Name="label1" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden">检测到新版本,是否下载?</Label>
            <Button Grid.Row="1" Height="23" Name="button1" VerticalAlignment="Center" Visibility="Hidden" Click="button1_Click">开始下载</Button>
            <Label Grid.Row="2" Margin="0" Name="label2" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Hidden">更新包已下载完毕,在程序关闭后将自动执行更新操作。</Label>
        </Grid>
    </Window>
    

    需注意的是,这里控件都被设置为Visibility="Hidden",我们将会在需要时再将其显示出来。

    编写后台代码:

    public Uri DownloadUri
    {
        get
        {
            return _DownloadUri;
        }
        set
        {
            _DownloadUri = value;
        }
    }
    private Uri _DownloadUri;
    
    public bool UpdateReady
    {
        get
        {
            return _UpdateReady;
        }
        set
        {
            _UpdateReady = value;
        }
    }
    private bool _UpdateReady;
    
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        var u = new MyApp.ServiceReference.UpdateSoapClient();
        var s=u.GetUpdate(System.Windows.Forms.Application.ProductVersion);
        if (!string.IsNullOrEmpty(s))
        {
            //获取相对于Web服务所在Uri的Uri
            DownloadUri = new Uri(u.Endpoint.ListenUri, s);
            label1.Visibility = button1.Visibility = Visibility.Visible;
        }
    }
    
    private void button1_Click(object sender, RoutedEventArgs e)
    {
        var c = new WebClient();
        c.DownloadFile(DownloadUri, System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "update.data"));
        UpdateReady = true;
        label2.Visibility = Visibility.Visible;
    }
    
    private void Window_Closed(object sender, EventArgs e)
    {
        if (UpdateReady)
        {
            Process.Start(System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "update.exe"));
        }
    }

    测试

    现在将主程序、附属文件和Update.exe放在一起,并将主程序及附属文件复制一份放到服务器端的App_data/Update/目录中,再添加一个“更新说明.txt”:

    image

    然后启动客户端程序进行测试,应该看到程序界面里什么都没有,因为客户端和服务器端程序版本是一致的。

    现在我们修改客户端版本号为1.0.0.1:

    image

    然后重新编译程序。

    因为服务器仅仅是判断版本号是否不同,而不是哪个更高,所以不仅仅是升级,降级更新也是可以的,我们来测试一下:

    image

    找到所谓的新版本了^^,点开始下载:

    image

    下载完成,这时目录里就有update.data这个文件了。

    现在关闭程序,等待10秒,让Update.exe完成更新:

    image

    可以看到,程序被降级为1.0.0.0了,而且那个“更新说明.txt”也被更新出来了。

    下载

    示例源代码:http://www.uushare.com/user/icesee/file/2338431

    本文的XPS版本:http://www.uushare.com/user/icesee/file/2338436

    注意:此博客已停止更新,并迁移至blog.SkyDev.cc,后续都将在新地址更新。



  • 相关阅读:
    POJ 2541 Binary Witch(逆序KMP,好题)
    POJ 2185 Milking Grid (KMP,求最小覆盖子矩阵,好题)
    POJ 3336 Count the string (KMP+DP,好题)
    POJ 1961 2406 (KMP,最小循环节,循环周期)
    POJ 3450 Corporate Identity (KMP,求公共子串,方法很妙)
    KMP模板,最小循环节
    BZOJ 2741 【FOTILE模拟赛】L(可持久化trie)
    BZOJ 2820 YY的GCD(莫比乌斯反演)
    VIJOS 1889 天真的因数分解(莫比乌斯反演,容斥原理)
    BZOJ 2440 完全平方数(莫比乌斯反演,容斥原理)
  • 原文地址:https://www.cnblogs.com/SkyD/p/1628566.html
Copyright © 2011-2022 走看看