java WebService + C# winform实现软件更新功能

    由于项目的需求的变动,客户想要把原来由javaEE开发的B/S架构一个系统平台换为C/S架构的,考虑到项目进度和效率的问题,项目组决定采用C#winform来实现客户端的开发,而服务器端直接引用原有的系统业务。考虑到客户端软件可能以后会不断地需要更新,因此做了一个软件自动更新的功能。闲话少说,转到正题!

首先我先要介绍一下该功能的总体实现思路:

首先考虑的是在服务端要有哪些方法来实现软件的更新功能呢?

一、软件需要更新,必然涉及到文件的读取操作,因此我们要有一个读取文件的方法;

二、软件更新的过程中需要用进度条来展示更新的进度,因此我们服务端还需要有一个获取文件大小的方法;

三、这是最重要的一点,就是客户端该如何来确认是否需要更新,更新那些文件?因此我们需要用一个xml文件来描述这些信息。

其次要考虑一下客户端的实现方式了,客户端应该如何实现呢?

一、客户端首先要判断软件是否需要更新,要更新那些文件,因此我们必须先要把服务器上对软件更新的xml描述文件先从服务端下载下来,然后与客户端上的xml文件进行比较,看是否需要更新;

二、若通过xml文件比较后,发现需要更新后,读取xml文件中需要更新的文件列表,然后依次下载需要更新的文件到临时的更新文件夹;

三、停止主程序进程,替换掉程序中原有的文件,最后关闭更新程序,启动主程序,更新完成!

 

实现程序更新的效果图:

 



 

 

现在我们就根据我们的总体实现思路来一步一步完成该应用的实现:

一、WebService的开发源码
根据上面的思路我们分析出实现该应用我们至少需要两个方法,一个是读取文件的方法,一个是获取文件大小的方法,本人采用的是JAX-WS 2.1来实现WebService的,采用其他的服务类库也可以,只要实现该服务就可以了,我的服务实现类如下:

 

Java代码 复制代码 收藏代码
  1. package com.updatesoft.service;   
  2.   
  3. import java.io.ByteArrayOutputStream;   
  • import java.io.File;   
  • import java.io.FileInputStream;   
  • import java.io.IOException;   
  • import java.net.URLDecoder;   
  •   
  • /**  
  •  * 更新软件操作类  
  •  * @author jin  
  •  *  
  •  */  
  • public class UpdateSoft {   
  •   
  •     /**  
  •      * 获取文件大小  
  •      * @param fileName 文件名称  
  •      * @return 文件大小(字节)  
  •      */  
  •     public long getFileSize(String fileName) {   
  •         int nFileLength = -1;      
  •         try {   
  •                
  •             String str = URLDecoder.decode(getClass().getClassLoader().getResource("com").toString(),"UTF-8");   
  •             str= str.substring(0, str.indexOf("WEB-INF/classes"));    
  •             str=str.substring(6);   
  •             System.out.println("路径:" + str);   
  •                
  •             File file = new File(str + fileName);   
  •             if (file.exists()) {   
  •                 FileInputStream fis = null;   
  •                 fis = new FileInputStream(file);   
  •                 nFileLength = fis.available();   
  •             } else {   
  •                 System.out.println("文件不存在");   
  •             }   
  •   
  •         }catch (IOException e) {      
  •             e.printStackTrace();      
  •         }catch (Exception e) {      
  •             e.printStackTrace();      
  •         }      
  •         System.out.println(nFileLength);   
  •         return nFileLength;   
  •     }   
  •   
  •     /**  
  •      * 根据偏移量和字节缓存大小分段获取文件字节数组  
  •      * @param fileName 文件名称  
  •      * @param offset 字节偏移量  
  •      * @param bufferSize 字节缓存大小  
  •      * @return 文件字节数组  
  •      */  
  •     public byte[] getUpdateFile(String fileName, int offset, int bufferSize) {   
  •         byte[] ret = null;      
  •         try {    
  •             String str = URLDecoder.decode(getClass().getClassLoader().getResource("com").toString(),"UTF-8");   
  •             str= str.substring(0, str.indexOf("WEB-INF/classes"));    
  •             str=str.substring(6);   
  •             File file = new File(str + fileName);   
  •                
  •             if (!file.exists()) {      
  •                 return null;      
  •             }      
  •             FileInputStream in = new FileInputStream(file);      
  •             ByteArrayOutputStream out = new ByteArrayOutputStream(1024);      
  •             byte[] b = new byte[1024];      
  •             int n;   
  •             int t = 0;   
  •             while ((n = in.read(b)) != -1) {    
  •                 if(t >= offset && t< offset + bufferSize){   
  •                     out.write(b, 0, n);   
  •                 }   
  •                 t += n;   
  •             }      
  •             in.close();      
  •             out.close();      
  •             ret = out.toByteArray();   
  •         } catch (IOException e) {      
  •             e.printStackTrace();      
  •         }      
  •         return ret;   
  •     }   
  • }  
package com.updatesoft.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLDecoder;

/**
 * 更新软件操作类
 * @author jin
 *
 */
public class UpdateSoft {

	/**
	 * 获取文件大小
	 * @param fileName 文件名称
	 * @return 文件大小(字节)
	 */
	public long getFileSize(String fileName) {
		int nFileLength = -1;   
		try {
			
			String str = URLDecoder.decode(getClass().getClassLoader().getResource("com").toString(),"UTF-8");
			str= str.substring(0, str.indexOf("WEB-INF/classes")); 
			str=str.substring(6);
			System.out.println("路径:" + str);
			
			File file = new File(str + fileName);
			if (file.exists()) {
				FileInputStream fis = null;
				fis = new FileInputStream(file);
			    nFileLength = fis.available();
			} else {
				System.out.println("文件不存在");
			}

		}catch (IOException e) {   
			e.printStackTrace();   
		}catch (Exception e) {   
			e.printStackTrace();   
		}   
		System.out.println(nFileLength);
		return nFileLength;
	}

	/**
	 * 根据偏移量和字节缓存大小分段获取文件字节数组
	 * @param fileName 文件名称
	 * @param offset 字节偏移量
	 * @param bufferSize 字节缓存大小
	 * @return 文件字节数组
	 */
	public byte[] getUpdateFile(String fileName, int offset, int bufferSize) {
		byte[] ret = null;   
		try { 
			String str = URLDecoder.decode(getClass().getClassLoader().getResource("com").toString(),"UTF-8");
			str= str.substring(0, str.indexOf("WEB-INF/classes")); 
			str=str.substring(6);
			File file = new File(str + fileName);
			
		    if (!file.exists()) {   
		        return null;   
		    }   
		    FileInputStream in = new FileInputStream(file);   
		    ByteArrayOutputStream out = new ByteArrayOutputStream(1024);   
		    byte[] b = new byte[1024];   
		    int n;
		    int t = 0;
		    while ((n = in.read(b)) != -1) { 
		    	if(t >= offset && t< offset + bufferSize){
		    		out.write(b, 0, n);
		    	}
		    	t += n;
		    }   
		    in.close();   
		    out.close();   
		    ret = out.toByteArray();
		} catch (IOException e) {   
		    e.printStackTrace();   
		}   
		return ret;
	}
}

 

客户端所需要调用的服务方法我们已经实现了,接下来我们需要准备我们软件更新的资源了(即需要更新的文件和更新文件的描述文件update.xml)。资源文件根据需求上传到服务器中,其中update.xml文件格式如下:

Xml代码 复制代码 收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <update>     
  3.     <forceUpdate>false</forceUpdate>     
  4.     <version>20100812</version>     
  5.     <subversion>1</subversion>     
  6.     <filelist count="5">     
  7.         <file name="music/陈瑞 - 白狐.mp3">true</file>  
  8.         <file name="music/韩红 - 擦肩而过.mp3">true</file>    
  9.         <file name="music/林俊杰 - 背对背拥抱.mp3">true</file>  
  10.         <file name="music/油菜花-成龙.mp3">true</file>  
  11.         <file name="music/郑智化 - 别哭我最爱的人.mp3">true</file>  
  12.     </filelist>     
  13.     <executeFile>SystemUpdateClient.exe</executeFile>     
  14. </update>  
<?xml version="1.0" encoding="UTF-8"?>
<update>  
    <forceUpdate>false</forceUpdate>  
    <version>20100812</version>  
    <subversion>1</subversion>  
    <filelist count="5">  
        <file name="music/陈瑞 - 白狐.mp3">true</file>
        <file name="music/韩红 - 擦肩而过.mp3">true</file> 
		<file name="music/林俊杰 - 背对背拥抱.mp3">true</file>
        <file name="music/油菜花-成龙.mp3">true</file>
		<file name="music/郑智化 - 别哭我最爱的人.mp3">true</file>
    </filelist>  
    <executeFile>SystemUpdateClient.exe</executeFile>  
</update>

 

根节点为updateforceUpdate为是否强制更新,ture则为是,false则为否;version为主版本号,subversion为次版本号,flielist为需要更新的文件列表,属性count指定需要更新的文件数,flie为文件节点,name属性指定文件名称,值true为需要更新,值false为不需要更新。executeFile指定软件更新完成后需要重新启动的可执行文件。

 

二、客户端的开发源码

客户端的实现也比较简单,本人采用的是vs2008的开发工具,在解决方案中新建一个软件更新的窗体,在窗体中拖入一个文本框和两个进度条,文本框用于显示更新过程,两个进度条一个用于显示总进度,一个显示单个文件进度。为了解决多线程环境中跨线程改写 ui 控件属性问题,我这里采用了代理方法,实现代码如下:

 

C#代码 复制代码 收藏代码
  1. using System;   
  2. using System.Collections.Generic;   
  3. using System.ComponentModel;   
  4. using System.Data;   
  5. using System.Drawing;   
  6. using System.Linq;   
  7. using System.Text;   
  8. using System.Windows.Forms;   
  9. using System.Threading;   
  10. using System.IO;   
  11. using System.Xml;   
  12.   
  13. namespace SystemUpdateClient   
  14. {   
  15.     public partial class update : Form   
  16.     {   
  17.         /// <summary>      
  18.         /// 每次下载并写入磁盘的文件数据大小(字节)      
  19.         /// </summary>      
  20.         private static int BUFFER_SIZE = 15 * 1024;      
  21.      
  22.         //把窗体改为单例模型      
  23.         private static update updateForm;      
  24.         public static update getUpdateForm()      
  25.         {      
  26.             if (updateForm == null)      
  27.             {      
  28.                 updateForm = new update();      
  29.             }      
  30.             return updateForm;      
  31.         }      
  32.         //构造函数改为私有,外部程序不可以使用 new() 来创建新窗体,保证了窗体唯一性      
  33.         private update()      
  34.         {         
  35.             InitializeComponent();      
  36.         }   
  37.   
  38.         //******** 定义代理方法,解决多线程环境中跨线程改写 ui 控件属性,开始 ********   
  39.   
  40.         //定义设置一个文本的委托方法(字符串)   
  41.         private delegate void setText(string log);   
  42.         //定义设置一个进度的委托方法(整型)   
  43.         private delegate void setProcess(int count);   
  44.   
  45.         //设置总进度条的最大数   
  46.         private void setProgressBar1_Maximum(int count)   
  47.         {   
  48.             progressBar1.Maximum = count;   
  49.         }   
  50.         //设置单文件进度条的最大数   
  51.         private void setProgressBar2_Maximum(int count)   
  52.         {   
  53.             progressBar2.Maximum = count;   
  54.         }   
  55.         //设置总进度条的当前值   
  56.         private void setProgressBar1_value(int count)   
  57.         {   
  58.             progressBar1.Value = count;   
  59.         }   
  60.         //设置单文件进度条当前值   
  61.         private void setProgressBar2_value(int count)   
  62.         {   
  63.             progressBar2.Value = count;   
  64.         }   
  65.         //设置总文件进度条步进进度   
  66.         private void addProgressBar1_value(int count)   
  67.         {   
  68.             if (progressBar1.Maximum > progressBar1.Value)   
  69.             {   
  70.                 progressBar1.Value += count;   
  71.             }   
  72.             else  
  73.             {   
  74.                 progressBar1.Value = progressBar1.Maximum;   
  75.             }   
  76.         }   
  77.         //设置单文件进度条步进进度   
  78.         private void addProgressBar2_value(int count)   
  79.         {   
  80.             if (progressBar2.Maximum > progressBar2.Value)   
  81.             {   
  82.                 progressBar2.Value += count;   
  83.             }   
  84.             else  
  85.             {   
  86.                 progressBar2.Value = progressBar2.Maximum;   
  87.             }   
  88.         }   
  89.         //设置文本框的值   
  90.         private void UpdateText(string log)   
  91.         {   
  92.             textBox1.Text += log;   
  93.         }   
  94.   
  95.         //******** 定义代理方法,解决多线程环境中跨线程改写 ui 控件属性  结束 ********   
  96.   
  97.         /// <summary>   
  98.         /// 窗体显示时,调用 invokeThread 方法   
  99.         /// </summary>   
  100.         /// <param name="sender"></param>   
  101.         /// <param name="e"></param>   
  102.         private void update_Shown(object sender, EventArgs e)   
  103.         {   
  104.             invokeThread();   
  105.         }   
  106.   
  107.         /// <summary>   
  108.         /// 开启一个线程,执行 update_function 方法   
  109.         /// </summary>   
  110.         void invokeThread()   
  111.         {   
  112.             Thread th = new Thread(new ThreadStart(update_function));   
  113.             th.Start();   
  114.         }   
  115.   
  116.         /// <summary>   
  117.         /// 自动更新方法,整合实现下面的业务逻辑。   
  118.         /// </summary>   
  119.         private void update_function()   
  120.         {   
  121.             //判断 位于本地客户端程序文件夹 update 是否存在   
  122.             if (Directory.Exists(Application.StartupPath + "/update"))   
  123.             {   
  124.                 //存在则删除,true 表示移除包含的子目录及文件   
  125.                 Directory.Delete("update/"true);   
  126.             }   
  127.             try{   
  128.                 //通过 webservice 从服务器端获取更新脚本文件 update.xml   
  129.                 getUpdateXMLFile();   
  130.             }   
  131.             catch (Exception e)   
  132.             {   
  133.                 MessageBox.Show("无法进行更新,访问服务器失败!\n\r原因:" + e.Message, "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);   
  134.             }   
  135.   
  136.             //判断强制更新开关   
  137.             if (isForceUpdate())   
  138.             {   
  139.                 //通过 webservice 从服务器端下载更新程序文件   
  140.                 downloadFiles();   
  141.             }   
  142.             else  
  143.             {   
  144.                 //比较版本号   
  145.                 if (verifyVersion())   
  146.                 {   
  147.                     //通过 webservice 从服务器端下载更新程序文件   
  148.                     downloadFiles();   
  149.                 }   
  150.             }   
  151.             DialogResult result = MessageBox.Show("更新完成!""提示", MessageBoxButtons.OK, MessageBoxIcon.Information);   
  152.             if (result == DialogResult.OK)   
  153.             {   
  154.                 //启动客户端主程序,退出更新程序   
  155.                 appExit();   
  156.             }   
  157.         }   
  158.   
  159.         /// <summary>   
  160.         /// 下载 update.xml   
  161.         /// </summary>   
  162.         private void getUpdateXMLFile()   
  163.         {   
  164.             //执行委托方法,更新文本控件内容   
  165.             textBox1.Invoke(new setText(this.UpdateText), new object[] { "正在从服务器下载 更新脚本文件 update.xml \r\n" });   
  166.                
  167.             //创建一个文件传送的 webservice 接口实例   
  168.             updateservice.UpdateSoftDelegateClient sendFileWS = new updateservice.UpdateSoftDelegateClient();   
  169.           
  170.             //通过 webservice接口 获取服务器上 update.xml 文件的长度。   
  171.             long fileSize = sendFileWS.getFileSize("update.xml");   
  172.             //判断本地客户端文件夹下 update 目录是否存在   
  173.             if (!Directory.Exists(Application.StartupPath + "/update"))   
  174.             {   
  175.                 //不存在则创建 update 目录   
  176.                 Directory.CreateDirectory(Application.StartupPath + "/update");   
  177.             }   
  178.             //通过定义文件缓冲区分块下载 update.xml 文件   
  179.             for (int offset = 0; offset < fileSize; offset += BUFFER_SIZE)   
  180.             {   
  181.                 //从服务器读取指定偏移值和指定长度的二进制文件字符数组   
  182.                 byte[] bytes = sendFileWS.getUpdateFile("update.xml", offset, BUFFER_SIZE);   
  183.                 //如果 字符数组不为空   
  184.                 if (bytes != null)   
  185.                 {   
  186.                     //以追加方式打开 update.xml 文件    
  187.                     using (FileStream fs = new FileStream(Application.StartupPath + "/update/update.xml", FileMode.Append))   
  188.                     {   
  189.                         //写入数据   
  190.                         fs.Write(bytes, 0, bytes.Length);   
  191.                         fs.Close();   
  192.                     }   
  193.                 }   
  194.             }   
  195.               
  196.   
  197.         }   
  198.   
  199.         /// <summary>   
  200.         /// 是否开启强制更新。   
  201.         /// </summary>   
  202.         /// <returns>true 开启强制更新,false 比较版本号后再更新</returns>   
  203.         private bool isForceUpdate()   
  204.         {   
  205.             try  
  206.             {   
  207.                 //开始解析 update/update.xml 新文件   
  208.                 XmlDocument doc = new XmlDocument();   
  209.                 doc.Load("update/update.xml");   
  210.                 XmlElement root = doc.DocumentElement;   
  211.                 //节点是否存在   
  212.                 if (root.SelectSingleNode("forceUpdate") != null)   
  213.                 {   
  214.                     //获取 forceUpdate 节点的内容   
  215.                     string forceUpdate = root.SelectSingleNode("forceUpdate").InnerText;   
  216.                     doc = null;   
  217.                     if (forceUpdate.Equals("true"))   
  218.                     {   
  219.                         textBox1.Invoke(new setText(this.UpdateText), new object[] { "强制更新开关已打开,不再匹配版本号。 \r\n" });   
  220.                         return true;   
  221.                     }   
  222.                     else  
  223.                     {   
  224.                         return false;   
  225.                     }   
  226.                 }   
  227.                 else  
  228.                 {   
  229.                     doc = null;   
  230.                     return false;   
  231.                 }   
  232.   
  233.   
  234.             }   
  235.             catch  
  236.             {   
  237.                 //发生异常,则更新程序,覆盖 update.xml   
  238.                 MessageBox.Show("版本文件解析异常,服务器端 update.xml 可能已经损坏,请联系管理员。""警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);   
  239.                 return true;   
  240.             }   
  241.         }   
  242.   
  243.   
  244.         /// <summary>   
  245.         ///  解析 update.xml 文件,比较version 和 subversion 判断是否有新版本   
  246.         /// </summary>   
  247.         /// <returns>true 有新版本,false 版本相同</returns>   
  248.         private bool verifyVersion()   
  249.         {   
  250.             try  
  251.             {   
  252.                 if (!File.Exists("update.xml"))   
  253.                 {   
  254.                     return true;   
  255.                 }   
  256.                 //开始解析 update.xml 旧文件   
  257.                 XmlDocument doc1 = new XmlDocument();   
  258.                 doc1.Load("update.xml");   
  259.                 XmlElement root1 = doc1.DocumentElement;   
  260.   
  261.                 //开始解析 update/update.xml 新文件   
  262.                 XmlDocument doc2 = new XmlDocument();   
  263.                 doc2.Load("update/update.xml");   
  264.                 XmlElement root2 = doc2.DocumentElement;   
  265.   
  266.                 if (root1.SelectSingleNode("version") != null && root1.SelectSingleNode("subversion") != null && root2.SelectSingleNode("version") != null && root2.SelectSingleNode("subversion") != null)   
  267.                 {   
  268.                     int old_version = Convert.ToInt32(root1.SelectSingleNode("version").InnerText);   
  269.                     int old_subversion = Convert.ToInt32(root1.SelectSingleNode("subversion").InnerText);   
  270.                     int new_version = Convert.ToInt32(root2.SelectSingleNode("version").InnerText);   
  271.                     int new_subversion = Convert.ToInt32(root2.SelectSingleNode("subversion").InnerText);   
  272.   
  273.                     doc1 = null;   
  274.                     doc2 = null;   
  275.   
  276.                     textBox1.Invoke(new setText(this.UpdateText), new object[] { "正在判断版本号...\r\n" });   
  277.                     //判断版本号和子版本号   
  278.                     if (old_version == new_version && old_subversion == new_subversion)   
  279.                     {   
  280.                         textBox1.Invoke(new setText(this.UpdateText), new object[] { "已经是最新版本,无需更新\r\n" });   
  281.                         return false;   
  282.                     }   
  283.                     else  
  284.                     {   
  285.                         textBox1.Invoke(new setText(this.UpdateText), new object[] { "发现新版本,开始读取更新列表 \r\n" });   
  286.                         return true;   
  287.                     }   
  288.                 }   
  289.                 else  
  290.                 {   
  291.                     textBox1.Invoke(new setText(this.UpdateText), new object[] { "无法解析版本号,将下载更新全部文件...\r\n" });   
  292.                     doc1 = null;   
  293.                     doc2 = null;   
  294.                     return true;   
  295.                 }   
  296.             }   
  297.             catch  
  298.             {   
  299.                 //发生异常,则更新程序,覆盖 update.xml   
  300.                 MessageBox.Show("版本文件解析异常,服务器端 update.xml 可能已经损坏,请联系管理员。""警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);   
  301.                    
  302.                 return true;   
  303.             }   
  304.         }   
  305.   
  306.         /// <summary>   
  307.         /// 解析 update.xml,下载更新文件   
  308.         /// </summary>   
  309.         public void downloadFiles()   
  310.         {   
  311.             //解析 update.xml   
  312.             XmlDocument doc = new XmlDocument();   
  313.             doc.Load("update/update.xml");   
  314.             XmlElement root = doc.DocumentElement;   
  315.             XmlNode fileListNode = root.SelectSingleNode("filelist");   
  316.             //获取更新文件的数量   
  317.             int fileCount = Convert.ToInt32(fileListNode.Attributes["count"].Value);   
  318.             //调用委托方法,更新控件内容。   
  319.             textBox1.Invoke(new setText(this.UpdateText), new object[] { "需更新文件数量 " + fileCount.ToString() + "\r\n" });   
  320.                
  321.             //结束 SystemUpdateClient.exe 进程   
  322.             System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();   
  323.             foreach (System.Diagnostics.Process process in processes)   
  324.             {   
  325.                 if (process.ProcessName == "SystemUpdateClient.exe")   
  326.                 {   
  327.                     process.Close();   
  328.                     break;   
  329.                 }   
  330.             }   
  331.   
  332.             //总文件大小,用于设置总进度条最大值   
  333.             long totalFileSize = 0;   
  334.   
  335.             //循环文件列表,获取总文件的大小   
  336.             for (int i = 0; i < fileCount; i++)   
  337.             {   
  338.                 XmlNode itemNode = fileListNode.ChildNodes[i];   
  339.                 //获取更新文件名   
  340.                 string fileName = itemNode.Attributes["name"].Value;   
  341.                 //获取需要更新文件的总大小,调用 webservice 接口   
  342.                 updateservice.UpdateSoftDelegateClient sendFileWS = new updateservice.UpdateSoftDelegateClient();   
  343.                 //获取文件长度(字节)   
  344.                 long fileSize = sendFileWS.getFileSize(fileName);   
  345.                 totalFileSize += fileSize;   
  346.             }   
  347.   
  348.             //调用委托方法,设置总进度条的最大值。   
  349.             progressBar1.Invoke(new setProcess(this.setProgressBar1_Maximum), new object[] { (int)(totalFileSize / BUFFER_SIZE) + 1 });   
  350.             //调用委托方法,更新控件内容。   
  351.             textBox1.Invoke(new setText(this.UpdateText), new object[] { "开始更新...\r\n" });   
  352.   
  353.             //循环文件列表   
  354.             for (int i = 0; i < fileCount; i++)   
  355.             {   
  356.                 XmlNode itemNode = fileListNode.ChildNodes[i];   
  357.                 //获取更新文件名   
  358.                 string fileName = itemNode.Attributes["name"].Value;   
  359.                 //调用委托方法,更新控件内容。   
  360.                 textBox1.Invoke(new setText(this.UpdateText), new object[] { "正在下载文件 " + fileName + "\r\n" });   
  361.                 //分块下载文件,调用 webservice 接口   
  362.                 updateservice.UpdateSoftDelegateClient sendFileWS = new updateservice.UpdateSoftDelegateClient();   
  363.                 //获取文件长度(字节)   
  364.                 long fileSize = sendFileWS.getFileSize(fileName);   
  365.                 //调用委托方法,更新进度条控件内容。   
  366.                 progressBar2.Invoke(new setProcess(this.setProgressBar2_Maximum), new object[] { (int)(fileSize / BUFFER_SIZE) + 1 });   
  367.                 progressBar2.Invoke(new setProcess(this.setProgressBar2_value), new object[] { 0 });   
  368.                 //通过 webservice 接口 循环读取文件数据块,每次向前步进 BUFFER_SIZE   
  369.                 for (int offset = 0; offset < fileSize; offset += BUFFER_SIZE)   
  370.                 {   
  371.                     Byte[] bytes = sendFileWS.getUpdateFile(fileName, offset, BUFFER_SIZE);   
  372.                     if (bytes != null)   
  373.                     {   
  374.                            
  375.                         if (fileName.LastIndexOf("/") != 0)   
  376.                         {   
  377.                             string newpath = fileName.Substring(0, fileName.LastIndexOf("/"));   
  378.                             if (!Directory.Exists(Application.StartupPath + "/update/" + newpath))   
  379.                             {   
  380.                                 //不存在则创建 update 目录   
  381.                                 Directory.CreateDirectory(Application.StartupPath + "/update/" + newpath);   
  382.                             }   
  383.                         }   
  384.                         //将下载的更新文件写入程序目录的 update 文件夹下   
  385.                         using (FileStream fs = new FileStream(Application.StartupPath + "/update/" + fileName, FileMode.Append))   
  386.                         {   
  387.                             fs.Write(bytes, 0, bytes.Length);   
  388.                             fs.Close();   
  389.                         }   
  390.                     }   
  391.                     bytes = null;   
  392.                     progressBar2.Invoke(new setProcess(this.addProgressBar2_value), new object[] { 1 });   
  393.                     progressBar1.Invoke(new setProcess(this.addProgressBar1_value), new object[] { 1 });   
  394.                 }   
  395.                 //替换文件   
  396.                 try  
  397.                 {   
  398.                     if (fileName.LastIndexOf("/") != 0)   
  399.                     {   
  400.                         string newpath = fileName.Substring(0, fileName.LastIndexOf("/"));   
  401.                         if (!Directory.Exists(Application.StartupPath + "/" + newpath))   
  402.                         {   
  403.                             //不存在则创建 update 目录   
  404.                             Directory.CreateDirectory(Application.StartupPath + "/" + newpath);   
  405.                         }   
  406.                     }   
  407.                     if (fileName != "SystemUpdateClient.XmlSerializers.dll" || fileName != "SystemUpdateClient.exe.config" || fileName != "SystemUpdateClient.pdb" || fileName != "SystemUpdateClient.exe")   
  408.                     {   
  409.                         File.Copy("update/" + fileName, fileName, true);   
  410.                     }   
  411.                 }   
  412.                 catch  
  413.                 {   
  414.                     textBox1.Invoke(new setText(this.UpdateText), new object[] { "无法复制" + fileName + "\r\n" });   
  415.                 }   
  416.                 //progressBar1.Invoke(new setProcess(this.addProgressBar1_value), new object[] { 1 });   
  417.             }   
  418.   
  419.             textBox1.Invoke(new setText(this.UpdateText), new object[] { "更新完成,更新程序正在做最后操作\r\n" });   
  420.   
  421.             //最后复制更新信息文件   
  422.             File.Copy("update/update.xml""update.xml"true);   
  423.   
  424.         }   
  425.   
  426.         /// <summary>   
  427.         /// 启动客户端主程序,退出更新程序   
  428.         /// </summary>   
  429.         private void appExit()   
  430.         {   
  431.             //判断 位于本地客户端程序文件夹 update 是否存在   
  432.             if (Directory.Exists(Application.StartupPath + "/update"))   
  433.             {   
  434.                 //存在则删除,true 表示移除包含的子目录及文件   
  435.                 Directory.Delete("update/"true);   
  436.             }   
  437.   
  438.             //获取主程序执行文件名   
  439.             XmlDocument doc = new XmlDocument();   
  440.             doc.Load("update.xml");   
  441.             XmlElement root = doc.DocumentElement;   
  442.             string executeFile = string.Empty;   
  443.             //节点是否存在   
  444.             if (root.SelectSingleNode("executeFile") != null)   
  445.             {   
  446.                 //获取 executeFile 节点的内容   
  447.                 executeFile = root.SelectSingleNode("executeFile").InnerText;   
  448.             }   
  449.             doc = null;   
  450.             //启动客户端程序   
  451.             System.Diagnostics.Process.Start(Application.StartupPath + @"\" + executeFile);   
  452.             //更新程序退出   
  453.             Application.Exit();   
  454.         }   
  455.           
  456.     }   
  457. }  
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.IO;
using System.Xml;

namespace SystemUpdateClient
{
    public partial class update : Form
    {
        /// <summary>   
        /// 每次下载并写入磁盘的文件数据大小(字节)   
        /// </summary>   
        private static int BUFFER_SIZE = 15 * 1024;   
  
        //把窗体改为单例模型   
        private static update updateForm;   
        public static update getUpdateForm()   
        {   
            if (updateForm == null)   
            {   
                updateForm = new update();   
            }   
            return updateForm;   
        }   
        //构造函数改为私有,外部程序不可以使用 new() 来创建新窗体,保证了窗体唯一性   
        private update()   
        {      
            InitializeComponent();   
        }

        //******** 定义代理方法,解决多线程环境中跨线程改写 ui 控件属性,开始 ********

        //定义设置一个文本的委托方法(字符串)
        private delegate void setText(string log);
        //定义设置一个进度的委托方法(整型)
        private delegate void setProcess(int count);

        //设置总进度条的最大数
        private void setProgressBar1_Maximum(int count)
        {
            progressBar1.Maximum = count;
        }
        //设置单文件进度条的最大数
        private void setProgressBar2_Maximum(int count)
        {
            progressBar2.Maximum = count;
        }
        //设置总进度条的当前值
        private void setProgressBar1_value(int count)
        {
            progressBar1.Value = count;
        }
        //设置单文件进度条当前值
        private void setProgressBar2_value(int count)
        {
            progressBar2.Value = count;
        }
        //设置总文件进度条步进进度
        private void addProgressBar1_value(int count)
        {
            if (progressBar1.Maximum > progressBar1.Value)
            {
                progressBar1.Value += count;
            }
            else
            {
                progressBar1.Value = progressBar1.Maximum;
            }
        }
        //设置单文件进度条步进进度
        private void addProgressBar2_value(int count)
        {
            if (progressBar2.Maximum > progressBar2.Value)
            {
                progressBar2.Value += count;
            }
            else
            {
                progressBar2.Value = progressBar2.Maximum;
            }
        }
        //设置文本框的值
        private void UpdateText(string log)
        {
            textBox1.Text += log;
        }

        //******** 定义代理方法,解决多线程环境中跨线程改写 ui 控件属性  结束 ********

        /// <summary>
        /// 窗体显示时,调用 invokeThread 方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void update_Shown(object sender, EventArgs e)
        {
            invokeThread();
        }

        /// <summary>
        /// 开启一个线程,执行 update_function 方法
        /// </summary>
        void invokeThread()
        {
            Thread th = new Thread(new ThreadStart(update_function));
            th.Start();
        }

        /// <summary>
        /// 自动更新方法,整合实现下面的业务逻辑。
        /// </summary>
        private void update_function()
        {
            //判断 位于本地客户端程序文件夹 update 是否存在
            if (Directory.Exists(Application.StartupPath + "/update"))
            {
                //存在则删除,true 表示移除包含的子目录及文件
                Directory.Delete("update/", true);
            }
            try{
                //通过 webservice 从服务器端获取更新脚本文件 update.xml
                getUpdateXMLFile();
            }
            catch (Exception e)
            {
                MessageBox.Show("无法进行更新,访问服务器失败!\n\r原因:" + e.Message, "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }

            //判断强制更新开关
            if (isForceUpdate())
            {
                //通过 webservice 从服务器端下载更新程序文件
                downloadFiles();
            }
            else
            {
                //比较版本号
                if (verifyVersion())
                {
                    //通过 webservice 从服务器端下载更新程序文件
                    downloadFiles();
                }
            }
            DialogResult result = MessageBox.Show("更新完成!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
            if (result == DialogResult.OK)
            {
                //启动客户端主程序,退出更新程序
                appExit();
            }
        }

        /// <summary>
        /// 下载 update.xml
        /// </summary>
        private void getUpdateXMLFile()
        {
            //执行委托方法,更新文本控件内容
            textBox1.Invoke(new setText(this.UpdateText), new object[] { "正在从服务器下载 更新脚本文件 update.xml \r\n" });
            
            //创建一个文件传送的 webservice 接口实例
            updateservice.UpdateSoftDelegateClient sendFileWS = new updateservice.UpdateSoftDelegateClient();
       
            //通过 webservice接口 获取服务器上 update.xml 文件的长度。
            long fileSize = sendFileWS.getFileSize("update.xml");
            //判断本地客户端文件夹下 update 目录是否存在
            if (!Directory.Exists(Application.StartupPath + "/update"))
            {
                //不存在则创建 update 目录
                Directory.CreateDirectory(Application.StartupPath + "/update");
            }
            //通过定义文件缓冲区分块下载 update.xml 文件
            for (int offset = 0; offset < fileSize; offset += BUFFER_SIZE)
            {
                //从服务器读取指定偏移值和指定长度的二进制文件字符数组
                byte[] bytes = sendFileWS.getUpdateFile("update.xml", offset, BUFFER_SIZE);
                //如果 字符数组不为空
                if (bytes != null)
                {
                    //以追加方式打开 update.xml 文件 
                    using (FileStream fs = new FileStream(Application.StartupPath + "/update/update.xml", FileMode.Append))
                    {
                        //写入数据
                        fs.Write(bytes, 0, bytes.Length);
                        fs.Close();
                    }
                }
            }
           

        }

        /// <summary>
        /// 是否开启强制更新。
        /// </summary>
        /// <returns>true 开启强制更新,false 比较版本号后再更新</returns>
        private bool isForceUpdate()
        {
            try
            {
                //开始解析 update/update.xml 新文件
                XmlDocument doc = new XmlDocument();
                doc.Load("update/update.xml");
                XmlElement root = doc.DocumentElement;
                //节点是否存在
                if (root.SelectSingleNode("forceUpdate") != null)
                {
                    //获取 forceUpdate 节点的内容
                    string forceUpdate = root.SelectSingleNode("forceUpdate").InnerText;
                    doc = null;
                    if (forceUpdate.Equals("true"))
                    {
                        textBox1.Invoke(new setText(this.UpdateText), new object[] { "强制更新开关已打开,不再匹配版本号。 \r\n" });
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    doc = null;
                    return false;
                }


            }
            catch
            {
                //发生异常,则更新程序,覆盖 update.xml
                MessageBox.Show("版本文件解析异常,服务器端 update.xml 可能已经损坏,请联系管理员。", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return true;
            }
        }


        /// <summary>
        ///  解析 update.xml 文件,比较version 和 subversion 判断是否有新版本
        /// </summary>
        /// <returns>true 有新版本,false 版本相同</returns>
        private bool verifyVersion()
        {
            try
            {
                if (!File.Exists("update.xml"))
                {
                    return true;
                }
                //开始解析 update.xml 旧文件
                XmlDocument doc1 = new XmlDocument();
                doc1.Load("update.xml");
                XmlElement root1 = doc1.DocumentElement;

                //开始解析 update/update.xml 新文件
                XmlDocument doc2 = new XmlDocument();
                doc2.Load("update/update.xml");
                XmlElement root2 = doc2.DocumentElement;

                if (root1.SelectSingleNode("version") != null && root1.SelectSingleNode("subversion") != null && root2.SelectSingleNode("version") != null && root2.SelectSingleNode("subversion") != null)
                {
                    int old_version = Convert.ToInt32(root1.SelectSingleNode("version").InnerText);
                    int old_subversion = Convert.ToInt32(root1.SelectSingleNode("subversion").InnerText);
                    int new_version = Convert.ToInt32(root2.SelectSingleNode("version").InnerText);
                    int new_subversion = Convert.ToInt32(root2.SelectSingleNode("subversion").InnerText);

                    doc1 = null;
                    doc2 = null;

                    textBox1.Invoke(new setText(this.UpdateText), new object[] { "正在判断版本号...\r\n" });
                    //判断版本号和子版本号
                    if (old_version == new_version && old_subversion == new_subversion)
                    {
                        textBox1.Invoke(new setText(this.UpdateText), new object[] { "已经是最新版本,无需更新\r\n" });
                        return false;
                    }
                    else
                    {
                        textBox1.Invoke(new setText(this.UpdateText), new object[] { "发现新版本,开始读取更新列表 \r\n" });
                        return true;
                    }
                }
                else
                {
                    textBox1.Invoke(new setText(this.UpdateText), new object[] { "无法解析版本号,将下载更新全部文件...\r\n" });
                    doc1 = null;
                    doc2 = null;
                    return true;
                }
            }
            catch
            {
                //发生异常,则更新程序,覆盖 update.xml
                MessageBox.Show("版本文件解析异常,服务器端 update.xml 可能已经损坏,请联系管理员。", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                
                return true;
            }
        }

        /// <summary>
        /// 解析 update.xml,下载更新文件
        /// </summary>
        public void downloadFiles()
        {
            //解析 update.xml
            XmlDocument doc = new XmlDocument();
            doc.Load("update/update.xml");
            XmlElement root = doc.DocumentElement;
            XmlNode fileListNode = root.SelectSingleNode("filelist");
            //获取更新文件的数量
            int fileCount = Convert.ToInt32(fileListNode.Attributes["count"].Value);
            //调用委托方法,更新控件内容。
            textBox1.Invoke(new setText(this.UpdateText), new object[] { "需更新文件数量 " + fileCount.ToString() + "\r\n" });
            
            //结束 SystemUpdateClient.exe 进程
            System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
            foreach (System.Diagnostics.Process process in processes)
            {
                if (process.ProcessName == "SystemUpdateClient.exe")
                {
                    process.Close();
                    break;
                }
            }

            //总文件大小,用于设置总进度条最大值
            long totalFileSize = 0;

            //循环文件列表,获取总文件的大小
            for (int i = 0; i < fileCount; i++)
            {
                XmlNode itemNode = fileListNode.ChildNodes[i];
                //获取更新文件名
                string fileName = itemNode.Attributes["name"].Value;
                //获取需要更新文件的总大小,调用 webservice 接口
                updateservice.UpdateSoftDelegateClient sendFileWS = new updateservice.UpdateSoftDelegateClient();
                //获取文件长度(字节)
                long fileSize = sendFileWS.getFileSize(fileName);
                totalFileSize += fileSize;
            }

            //调用委托方法,设置总进度条的最大值。
            progressBar1.Invoke(new setProcess(this.setProgressBar1_Maximum), new object[] { (int)(totalFileSize / BUFFER_SIZE) + 1 });
            //调用委托方法,更新控件内容。
            textBox1.Invoke(new setText(this.UpdateText), new object[] { "开始更新...\r\n" });

            //循环文件列表
            for (int i = 0; i < fileCount; i++)
            {
                XmlNode itemNode = fileListNode.ChildNodes[i];
                //获取更新文件名
                string fileName = itemNode.Attributes["name"].Value;
                //调用委托方法,更新控件内容。
                textBox1.Invoke(new setText(this.UpdateText), new object[] { "正在下载文件 " + fileName + "\r\n" });
                //分块下载文件,调用 webservice 接口
                updateservice.UpdateSoftDelegateClient sendFileWS = new updateservice.UpdateSoftDelegateClient();
                //获取文件长度(字节)
                long fileSize = sendFileWS.getFileSize(fileName);
                //调用委托方法,更新进度条控件内容。
                progressBar2.Invoke(new setProcess(this.setProgressBar2_Maximum), new object[] { (int)(fileSize / BUFFER_SIZE) + 1 });
                progressBar2.Invoke(new setProcess(this.setProgressBar2_value), new object[] { 0 });
                //通过 webservice 接口 循环读取文件数据块,每次向前步进 BUFFER_SIZE
                for (int offset = 0; offset < fileSize; offset += BUFFER_SIZE)
                {
                    Byte[] bytes = sendFileWS.getUpdateFile(fileName, offset, BUFFER_SIZE);
                    if (bytes != null)
                    {
                        
                        if (fileName.LastIndexOf("/") != 0)
                        {
                            string newpath = fileName.Substring(0, fileName.LastIndexOf("/"));
                            if (!Directory.Exists(Application.StartupPath + "/update/" + newpath))
                            {
                                //不存在则创建 update 目录
                                Directory.CreateDirectory(Application.StartupPath + "/update/" + newpath);
                            }
                        }
                        //将下载的更新文件写入程序目录的 update 文件夹下
                        using (FileStream fs = new FileStream(Application.StartupPath + "/update/" + fileName, FileMode.Append))
                        {
                            fs.Write(bytes, 0, bytes.Length);
                            fs.Close();
                        }
                    }
                    bytes = null;
                    progressBar2.Invoke(new setProcess(this.addProgressBar2_value), new object[] { 1 });
                    progressBar1.Invoke(new setProcess(this.addProgressBar1_value), new object[] { 1 });
                }
                //替换文件
                try
                {
                    if (fileName.LastIndexOf("/") != 0)
                    {
                        string newpath = fileName.Substring(0, fileName.LastIndexOf("/"));
                        if (!Directory.Exists(Application.StartupPath + "/" + newpath))
                        {
                            //不存在则创建 update 目录
                            Directory.CreateDirectory(Application.StartupPath + "/" + newpath);
                        }
                    }
                    if (fileName != "SystemUpdateClient.XmlSerializers.dll" || fileName != "SystemUpdateClient.exe.config" || fileName != "SystemUpdateClient.pdb" || fileName != "SystemUpdateClient.exe")
                    {
                        File.Copy("update/" + fileName, fileName, true);
                    }
                }
                catch
                {
                    textBox1.Invoke(new setText(this.UpdateText), new object[] { "无法复制" + fileName + "\r\n" });
                }
                //progressBar1.Invoke(new setProcess(this.addProgressBar1_value), new object[] { 1 });
            }

            textBox1.Invoke(new setText(this.UpdateText), new object[] { "更新完成,更新程序正在做最后操作\r\n" });

            //最后复制更新信息文件
            File.Copy("update/update.xml", "update.xml", true);

        }

        /// <summary>
        /// 启动客户端主程序,退出更新程序
        /// </summary>
        private void appExit()
        {
            //判断 位于本地客户端程序文件夹 update 是否存在
            if (Directory.Exists(Application.StartupPath + "/update"))
            {
                //存在则删除,true 表示移除包含的子目录及文件
                Directory.Delete("update/", true);
            }

            //获取主程序执行文件名
            XmlDocument doc = new XmlDocument();
            doc.Load("update.xml");
            XmlElement root = doc.DocumentElement;
            string executeFile = string.Empty;
            //节点是否存在
            if (root.SelectSingleNode("executeFile") != null)
            {
                //获取 executeFile 节点的内容
                executeFile = root.SelectSingleNode("executeFile").InnerText;
            }
            doc = null;
            //启动客户端程序
            System.Diagnostics.Process.Start(Application.StartupPath + @"\" + executeFile);
            //更新程序退出
            Application.Exit();
        }
       
    }
}

 

通过源代码大家可以通过方法update_function()看出该应用的流程来,它首先是从服务端下载update.xml文件(调用getUpdateXMLFile()),根据下载的xml文件判断是否需要强制更新(调用isForceUpdate()),若是需要强制更新,那么将会强制更新所有的文件(调用downloadFiles()),若不需要强制更新则比较版本号(调用verifyVersion()),若版本号不同,则更新客户端软件,执行更新操作(调用downloadFiles()),更新完成后退出更新程序,启动主程序的可执行文件(调用appExit())。

到此我们整个软件更新应用算是已经完成了,关于代码的具体含义,方法的执行内容,大家看一下代码就明白了,很好理解的! 

posted on 2011-09-15 10:58  老有所依  阅读(408)  评论(0)    收藏  举报

导航