客户端程序自动更新(升级)的方式


一、C/S自动更新原理

 C/S程序自动升级是一个很重要的功能,原理其实很简单,一般包含两个程序一个是主程序,也就是除了升级功能以外的程序,另一个就是升级程序,常见的360,金山安全卫士都是这样。

 主要包括以下几点:   1 比较版本  2下载文件  3更新文件 4启动主程序。但其中的需要注意的细节很多。 

    一般服务端会有一个配置文件包含最新更新的文件信息的配置文件,当然这些更新信息也可以存到数据库,或者其他地方。客户端(也就是需要更新的那部分程序)也有一个配置文件包含客户端版本信息,这些信息可以存到专门的一个配置文件中,或者是config文件中,没有一定的规定,可以根据实际设计。

   在客户端程序启动时,先启动更新程序通过比较本地版本和服务端最新的版本信息判断是否有新版本,如果有可以直接下载,下载完成替换成功后并更新客户端版本信息,启动主程序。

            缺点:如果更新速度由于更新的文件很大或者网速很慢,用户不得不等待很长时间,直到下载完成或者下载失败。

            优点:处理完成后,启动的直接就是更新后的程序。不会出现由于主程序在运行导致替换文件时提示文件在使用,不能替换之类的错误。

   另一种方法是, 在客户端段程序启动时,启动更新程序,但更新程序不做版本判断,到客户端更新目录下检查有没有下载的新版本,如果有就更新主程序并更新客户端版本信息,然后启动主程序,如果没有就直接启动主程序。由主程序判断是否有新版本,并在后台下载把文件放到客户端更新目录中,下载完成后,提示用户退出主程序,重新启动,在启动时由更新程序并更新客户端和客户端版本信息。    

            缺点:由于下载是在主程序的后台运行,可能会影响主程序的处理速度。

            优点:避免了由于下载导致用户长时间的等待。

1 比较版本

    比较依据:

    可以通过文件的最后修改时间,或者使用文件版本作为比较依据,使用文件最后修改时间显然不是标准的做法,但也没有错误,但需要注意日期的格式一定要统一,避免日  期格式不一致导致错误。可以使用Fileinfo类获取最后修改时间,注意时间应该取服务器时间,编译程序集的机器时间应该相同,否则可能导致混乱。

FIleInfo类官网参考

   使用文件版本作为标准,则每次修改时必须修改版本号,C#程序就是要修AssemblyInfo.cs文件中的内容了,多了一步,规范多了。Version类处理版本信息并比较。 

  1. Assembly thisAssem = Assembly.GetExecutingAssembly();  
  2.      AssemblyName thisAssemName = thisAssem.GetName();  
  3.      Version ver = thisAssemName.Version;  

Version类官网参考

  当然也有其他的方式,例如MD5校验值比较,文件大小比较,之类的方法。不过个人认为文件大小缺陷很明显,新版本文件就一定比旧文件大吗?不一定吧。重构是可能变小的。

当然如果考虑客户端有不同的版本,都需要升级到最新的版本,显然不同的版本对应的升级文件不同,会更复杂,比较的信息也更多。

   获取服务端版本信息:

    如果服务端的版本信息存在数据库,直接读取数据库,就可以获取。如果存在配置文件,则可以通过webservice方法获取,或者请求一个网页 通过Response.Write();的方式获取信息,当然这两种方式都要建立虚拟目录或者网站。

2下载文件

  存储位置:

     如果新版本的文件存在数据库,就直接读取数据库,不过这种方式个人不建议使用,例如更新文件很大时性能不是很好。

    存在固定虚拟目录的指定路径下,不失为一种好的方式,但客户端要下载,所以要注意一定要分配下载权限。

下载方式:

   直接向通过虚拟路径发出请求,下载文件,由于虚拟路径有下载权限,如果更新需要判断是否有权限,例如要交费后才能下载则不好处理。

  另一种方式是向一个网页发送请求,传递不同的查询字符串,网页 通过Response.BinaryWrite();的方式下载文件,则可以判断权限,当然麻烦一些是避免不了的。

下载文件代码

  1. Uri uri = new Uri(downFileUrl + localFileName);  
  2.               HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);  
  3.               request.Credentials = CredentialCache.DefaultCredentials;  
  4.               request.MaximumAutomaticRedirections = 4;  
  5.               localFileName = Path.GetFileName(localFileName);  
  6.               using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())  
  7.               {  
  8.                   Stream receiveStream = response.GetResponseStream();  
  9.                   string newPath = Path.Combine(tempFold, localFileName);  
  10.                   using (FileStream fs = new FileStream(newPath, FileMode.Create))  
  11.                   {  
  12.                       Byte[] buffer = new Byte[4096];  
  13.                       int bytesRead = receiveStream.Read(buffer, 0, buffer.Length);  
  14.                       while (bytesRead > 0){  
  15.                           fs.Write(buffer, 0, bytesRead);  
  16.                           bytesRead = receiveStream.Read(buffer, 0, buffer.Length);  
  17.                       }  
  18.                   }  
  19.                   receiveStream.Close();  
  20.               }  

3更新文件

      更新类型:

     直接替换的,例如修改了bug,直接替换的。

     新增加的,例如新增加的功能做成了新的类库。

    需要删除的,例如有些功能由于重构或者使用了了新方法不需要的。

     需要执行的,例如写注册表,注册COM组件的。

     每一种处理方式都不一样,需要根据类型分开处理

     缺点:升级后,没办法取消升级,像windows的补丁程序可以安装,可以卸载的原理,目前还没有研究明白,希望知道的牛人指导。

    当然也可以简单的先卸载,再安装,对于配置文件之类的信息特殊处理一下也可以。

   当然如果考虑客户端有不同的版本,都需要升级到最新的版本,显然不同的版本对应的升级文件不同,会更复杂,但基本原理却不变。

4启动主程序

  主程序路径的获取:

   相对路径   主程序,更新程序,都使用相对路径,缺点是一旦相对路径确定后,后续的更新就不能更改这种目录关系。

  注册表  路径都存入注册表,需要时通过注册表交互,主程序写注册表,更新程序读取注册表,缺点是读写注册表需要权限,写的路径也要固定,后续的更新不能改变写在注册表中的位置,也就是注册表路径。

运行程序代码

 

  1. private static void RunFile(string dir, string localFileName){  
  2.           string info = "运行程序" + localFileName;  
  3.           try{  
  4.               if (File.Exists(Path.Combine(dir, localFileName))){  
  5.                   Process myProcess = new Process();  
  6.                   ProcessStartInfo psi = new ProcessStartInfo();  
  7.                   psi.FileName = localFileName;  
  8.                   psi.WorkingDirectory = dir;  
  9.                   psi.UseShellExecute = false;  
  10.                   psi.RedirectStandardError = true;  
  11.                   psi.CreateNoWindow = true;  
  12.                   psi.RedirectStandardOutput = true;  
  13.                   psi.WindowStyle = ProcessWindowStyle.Hidden;  
  14.                   myProcess.StartInfo = psi;  
  15.                   myProcess.Start();  
  16.   
  17.                   string error = myProcess.StandardError.ReadToEnd();  
  18.                   string output = myProcess.StandardOutput.ReadToEnd();  
  19.                   myProcess.WaitForExit();  
  20.                   myProcess.Close();  
  21.                   if (error != string.Empty){  
  22.                    Log.Write("StandardError:" + error);  
  23.                   }  
  24.                   if (output != string.Empty){  
  25.                       Log.Write("StandardOutput:" + output);  
  26.                   }  
  27.                   Log.LogProcessEnd(info);  
  28.               }  
  29.           }  
  30.           catch (Exception ex){  
  31.              Log.Write(info + "出错");  
  32.              Log.LogException(ex);  
  33.               throw ex;  
  34.           }  
  35.       }  
  36.   }  

二、使用HttpWebRequest自动更新客户端
更新客户端应用程序,对于采用Socket实现的,可以采用Socket从服务器端下载;对于其它方式, 一般可以采用以HttpWebRequest/WebClient的方式予以下载,但WebClient在下载的时候无法看到下载进度,因此,使用HttpWebRequest 下载文件,根据读取到的流长度,确定当前下载的数据量,以反应当前下载的进度。

    找了半天也没有找到在那儿可以上传文件,所以只好将文件上传到博客园。

下载地址:http://www.cnblogs.com/Files/bluedream/Update.rar

说明:

在客户端,用户实际运行的是更新程序,在更新程序检查完成后,再运行实际的客户端。当然,这个对用户是隐藏的。

1、DownloadFile.cs: 使用HttpWebRequest下载指定URL的文件

2、EventArgs.cs:委托及事件

3、FormUpdate.cs:下载时的UI处理

4、Global.cs和UpdateUtility.cs通用处理函数库

5、Client.cs:更新客户端的应用程序

6、Update文件:客户端更新配置文件,在更新时,应用程序先读取Update文件,然后根据Update文件中存储的远程服务器URL地址,读取远程更新文件,接着比较远程服务器配置文件与本地配置文件及本地文件相比较,确定更新列表,然后下载文件;下载完成后,覆盖本地文件;再删除临时文件;最后调用本地配置文件指定的更新完成后应运行的应用程序。


三、JAVA自动更新客户端

最近由于一个工程需要做应用程序启动时,自动更新的项目
在GOOGLE上找了半天也没见到什么比较好的办法
自己动手写了一个通过版本号检查网络上是不是存在新的更新文件,并自动通过HTTP下载文件的程序
希望对正在找此类程序的朋友有帮助

本地文件需要一个ver.txt  此文件内容为本地软件版本号
网络上我直接在一个页面上打印出网络存在的版本号
例如,这个例子里,我在 http://XXX.XXX.XXX/AutoUpdate/ver  这里直接打印出版本号

源文件:http://211.136.109.100/beiouwolf/AutoUpdate.rar

 

[java]  view plain  copy
 print ?
  1. import javax.swing.*;  
  2. import java.awt.*;  
  3. import java.net.*;  
  4. import java.io.*;  
  5.   
  6. public class CheckUpdate extends JFrame {  
  7.     JFrame c = this;  
  8.   
  9.     public CheckUpdate() {  
  10.         //设置窗体属性  
  11.         setAttb();  
  12.   
  13.         JLabel title = new JLabel("正在检查网络上的更新资源");  
  14.         this.add(title, BorderLayout.NORTH);  
  15.         JTextArea msg = new JTextArea();  
  16.         this.add(msg, BorderLayout.CENTER);  
  17.         JLabel process = new JLabel();  
  18.         this.add(process, BorderLayout.SOUTH);  
  19.           
  20.         //启动更新线程  
  21.         new Check(msg, process).start();  
  22.     }  
  23.   
  24.     private class Check extends Thread {  
  25.         //标识,是否存在新的更新文件  
  26.         private boolean isUpdated = false;  
  27.         //保存最新的版本  
  28.         String netVersion;  
  29.         //本地版本文件名  
  30.         String LocalVerFileName = "ver.txt";  
  31.   
  32.         //显示信息  
  33.         private JTextArea msg;  
  34.         private JLabel process;  
  35.   
  36.         public Check(JTextArea msg, JLabel process) {  
  37.             this.msg = msg;  
  38.             this.process = process;  
  39.         }  
  40.   
  41.         public void run() {  
  42.             //更新文件版本标识URL  
  43.             String versionUrl = "http://XXX.XXX.XXX/AutoUpdate/ver";  
  44.   
  45. /**//* 
  46. 这里是通过HTTP访问一个页面,以取得网络上的版本号 
  47. 比如这里就是在这个页面直接打印出 6.19.1.1 
  48. 然后把这个版本号比对本地的版本号,如果版本号不同的话,就从网络上下载新的程序并覆盖现有程序 
  49.  
  50. */  
  51.   
  52.             URL url = null;  
  53.             InputStream is = null;  
  54.             InputStreamReader isr = null;  
  55.             BufferedReader netVer = null;  
  56.   
  57.             //读取网络上的版本号  
  58.             try {  
  59.                 url = new URL(versionUrl);  
  60.                 is = url.openStream();  
  61.                 isr = new InputStreamReader(is);  
  62.   
  63.                 netVer = new BufferedReader(isr);  
  64.                 String netVerStr = netVer.readLine();  
  65.                 String localVerStr = getNowVer();  
  66.   
  67.                 if (netVerStr.equals(localVerStr)) {  
  68.                     msg.append("当前文件是最新版本\n");  
  69.                     isUpdated = false;  
  70.                 } else {  
  71.                     msg.append("存在更新文件,现在开始更新\n");  
  72.                     isUpdated = true;  
  73.                     netVersion = netVerStr;  
  74.                 }  
  75.   
  76.             } catch (MalformedURLException ex) {  
  77.             } catch (IOException ex) {  
  78.             } finally {  
  79.                 //释放资源  
  80.                 try {  
  81.                     netVer.close();  
  82.                     isr.close();  
  83.                     is.close();  
  84.                 } catch (IOException ex1) {  
  85.                 }  
  86.             }  
  87.   
  88.             //如果版本不同,下载网络上的文件,更新本地文件  
  89.             if (isUpdated) {  
  90.                 //本地需要被更新的文件  
  91.                 File oldFile = new File("client.exe");  
  92.                 //缓存网络上下载的文件  
  93.                 File newFile = new File("temp.exe");  
  94.                   
  95.                 //网络上的文件位置  
  96.                 String updateUrl =  
  97.                         "http://XXX.XXX.XXX/downloads/simpkle.exe";  
  98.   
  99.                 HttpURLConnection httpUrl = null;  
  100.                 BufferedInputStream bis = null;  
  101.                 FileOutputStream fos = null;  
  102.   
  103.                 try {  
  104.                     //打开URL通道  
  105.                     url = new URL(updateUrl);  
  106.                     httpUrl = (HttpURLConnection) url.openConnection();  
  107.   
  108.                     httpUrl.connect();  
  109.   
  110.                     byte[] buffer = new byte[1024];  
  111.   
  112.                     int size = 0;  
  113.   
  114.                     is = httpUrl.getInputStream();  
  115.                     bis = new BufferedInputStream(is);  
  116.                     fos = new FileOutputStream(newFile);  
  117.   
  118.                     msg.append("正在从网络上下载新的更新文件\n");  
  119.   
  120.                     //保存文件  
  121.                     try {  
  122.                         int flag = 0;  
  123.                         int flag2 = 0;  
  124.                         while ((size = bis.read(buffer)) != -1) {  
  125.                             //读取并刷新临时保存文件  
  126.                             fos.write(buffer, 0, size);  
  127.                             fos.flush();  
  128.   
  129.                             //模拟一个简单的进度条  
  130.                             if (flag2 == 99) {  
  131.                                 flag2 = 0;  
  132.                                 process.setText(process.getText() + ".");  
  133.                             }  
  134.                             flag2++;  
  135.                             flag++;  
  136.                             if (flag > 99 * 50) {  
  137.                                 flag = 0;  
  138.                                 process.setText("");  
  139.                             }  
  140.                         }  
  141.                     } catch (Exception ex4) {  
  142.                         System.out.println(ex4.getMessage());  
  143.                     }  
  144.   
  145.                     msg.append("\n文件下载完成\n");  
  146.   
  147.                     //把下载的临时文件替换原有文件  
  148.                     CopyFile(oldFile,newFile);  
  149.                       
  150.                     //把本地版本文件更新为网络同步  
  151.                     UpdateLocalVerFile();  
  152.   
  153.                 } catch (MalformedURLException ex2) {  
  154.                 } catch (IOException ex) {  
  155.                     msg.append("文件读取错误\n");  
  156.                 } finally {  
  157.                     try {  
  158.                         fos.close();  
  159.                         bis.close();  
  160.                         is.close();  
  161.                         httpUrl.disconnect();  
  162.                     } catch (IOException ex3) {  
  163.                     }  
  164.                 }  
  165.             }  
  166.   
  167.             //启动应用程序  
  168.             try {  
  169.                 msg.append("启动应用程序");  
  170.                 Thread.sleep(500);  
  171.                 Process p = Runtime.getRuntime().exec("client.exe");  
  172.             } catch (IOException ex5) {  
  173.             } catch (InterruptedException ex) {  
  174.             }  
  175.               
  176.             //退出更新程序  
  177.             System.exit(0);  
  178.         }  
  179. //复制文件  
  180.         private void CopyFile(File oldFile, File newFile) {  
  181.             FileInputStream in = null;  
  182.             FileOutputStream out = null;  
  183.               
  184.             try {  
  185.                 if(oldFile.exists()){  
  186.                     oldFile.delete();  
  187.                 }  
  188.                 in = new FileInputStream(newFile);  
  189.                 out = new FileOutputStream(oldFile);  
  190.   
  191.                 byte[] buffer = new byte[1024 * 5];  
  192.                 int size;  
  193.                 while ((size = in.read(buffer)) != -1) {  
  194.                     out.write(buffer, 0, size);  
  195.                     out.flush();  
  196.                 }  
  197.             } catch (FileNotFoundException ex) {  
  198.             } catch (IOException ex) {  
  199.             } finally {  
  200.                 try {  
  201.                     out.close();  
  202.                     in.close();  
  203.                 } catch (IOException ex1) {  
  204.                 }  
  205.             }  
  206.   
  207.         }  
  208.   
  209.         private void UpdateLocalVerFile() {  
  210.             //把本地版本文件更新为网络同步  
  211.             FileWriter verOS = null;  
  212.             BufferedWriter bw = null;  
  213.             try {  
  214.                 verOS = new FileWriter(LocalVerFileName);  
  215.   
  216.                 bw = new BufferedWriter(verOS);  
  217.                 bw.write(netVersion);  
  218.                 bw.flush();  
  219.   
  220.             } catch (IOException ex) {  
  221.             } finally {  
  222.                 try {  
  223.                     bw.close();  
  224.                     verOS.close();  
  225.                 } catch (IOException ex1) {  
  226.                 }  
  227.             }  
  228.         }  
  229.   
  230.         private String getNowVer() {  
  231.             //本地版本文件  
  232.             File verFile = new File(LocalVerFileName);  
  233.   
  234.             FileReader is = null;  
  235.             BufferedReader br = null;  
  236.   
  237.             //读取本地版本  
  238.             try {  
  239.                 is = new FileReader(verFile);  
  240.   
  241.                 br = new BufferedReader(is);  
  242.                 String ver = br.readLine();  
  243.   
  244.                 return ver;  
  245.             } catch (FileNotFoundException ex) {  
  246.                 msg.append("本地版本文件未找到\n");  
  247.             } catch (IOException ex) {  
  248.                 msg.append("本地版本文件读取错误\n");  
  249.             } finally {  
  250.                 //释放资源  
  251.                 try {  
  252.                     br.close();  
  253.                     is.close();  
  254.                 } catch (IOException ex1) {  
  255.                 }  
  256.             }  
  257.             return "";  
  258.         }  
  259.     }  
  260.   
  261.   
  262.     private void setAttb() {  
  263.         //窗体设置  
  264.         this.setTitle("Auto Update");  
  265.         this.setSize(200150);  
  266.         this.setLayout(new BorderLayout());  
  267.         this.setDefaultCloseOperation(EXIT_ON_CLOSE);  
  268.   
  269.         // 窗体居中  
  270.         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();  
  271.         Dimension frameSize = this.getSize();  
  272.         if (frameSize.height > screenSize.height) {  
  273.             frameSize.height = screenSize.height;  
  274.         }  
  275.         if (frameSize.width > screenSize.width) {  
  276.             frameSize.width = screenSize.width;  
  277.         }  
  278.         this.setLocation((screenSize.width - frameSize.width) / 2,  
  279.                          (screenSize.height - frameSize.height) / 2);  
  280.     }  
  281.   
  282.     public static void main(String[] args) {  
  283.         CheckUpdate checkupdate = new CheckUpdate();  
  284.         checkupdate.setVisible(true);  
  285.     }  
  286. }  


你可能感兴趣的:(网络通信)