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

系统 1605 0

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

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

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

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

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

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

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

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

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

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

 

实现程序更新的效果图:

 


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

 

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

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

 

    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 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>
  

 

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

 

二、 客户端的开发源码

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

 

    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() )。

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

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


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论