- 浏览: 15332 次
- 性别:
- 来自: 北京
最新评论
-
风间苍月:
你这里面通过STRUTS2的方式获取session里面的内容。 ...
Spring3.0 + 自定义注解实现操作日志记录功能 -
dashuji:
我运行 时也 没有效果~! 什么情况
Spring3.0 + 自定义注解实现操作日志记录功能 -
wangming2012:
[*]引用[u][/u][i][/i][*]
Spring3.0 + 自定义注解实现操作日志记录功能 -
squall140:
这个帖子写的真好,学习了!
Spring3.0 + 自定义注解实现操作日志记录功能 -
mobydick2:
请教楼主:parseParames这个参数转换的方法在执行JS ...
Spring3.0 + 自定义注解实现操作日志记录功能
由于项目的需求的变动,客户想要把原来由javaEE开发的B/S架构一个系统平台换为C/S架构的,考虑到项目进度和效率的问题,项目组决定采用C#的winform来实现客户端的开发,而服务器端直接引用原有的系统业务。考虑到客户端软件可能以后会不断地需要更新,因此做了一个软件自动更新的功能。闲话少说,转到正题!
首先我先要介绍一下该功能的总体实现思路:
首先考虑的是在服务端要有哪些方法来实现软件的更新功能呢?
一、软件需要更新,必然涉及到文件的读取操作,因此我们要有一个读取文件的方法;
二、软件更新的过程中需要用进度条来展示更新的进度,因此我们服务端还需要有一个获取文件大小的方法;
三、这是最重要的一点,就是客户端该如何来确认是否需要更新,更新那些文件?因此我们需要用一个xml文件来描述这些信息。
其次要考虑一下客户端的实现方式了,客户端应该如何实现呢?
一、客户端首先要判断软件是否需要更新,要更新那些文件,因此我们必须先要把服务器上对软件更新的xml描述文件先从服务端下载下来,然后与客户端上的xml文件进行比较,看是否需要更新;
二、若通过xml文件比较后,发现需要更新后,读取xml文件中需要更新的文件列表,然后依次下载需要更新的文件到临时的更新文件夹;
三、停止主程序进程,替换掉程序中原有的文件,最后关闭更新程序,启动主程序,更新完成!
实现程序更新的效果图:
现在我们就根据我们的总体实现思路来一步一步完成该应用的实现:
一、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())。
到此我们整个软件更新应用算是已经完成了,关于代码的具体含义,方法的执行内容,大家看一下代码就明白了,很好理解的!
相关推荐
为了实现C#与Java之间的互操作,我们需要依赖于互操作性技术,如.NET的System.ServiceModel.Channels包,它可以处理SOAP消息。 在标签中提到了"Spring WebService",这是Spring对Web服务的支持。Spring WebService...
使用 C# 语言调用 Java 发布的 WebService 可以帮助我们实现跨语言通信,提高系统的灵活性和可扩展性。 知识点 1.C# 调用 Java 发布的 WebService 需要使用 Web 引用。 2.使用 C# 语言调用 Java 发布的 WebService ...
在C# WinForm应用中集成Web服务,主要是为了实现客户端与服务器端的通信,以便调用远程服务功能。本文将详细解析如何在C# WinForm应用程序中动态添加Web服务,并着重探讨命名空间和动态调用Java XML的相关技术点。 ...
C#语言设计的目标是结合C++的性能和Visual Basic的易用性,它也吸收了Java语言的一些特性。C#广泛应用于Web应用程序、Windows应用程序、分布式应用、网络服务、游戏开发以及各种其他类型的软件项目。 .NET平台是...
由于项目需求,需要在C#中压缩,然后在java里解压缩,或者...Java,用Session Bean建立了一个简单的WebService,提供一个简单的调用SayHello,然后C#里建立一个winform应用,添加服务引用,引用Java的webservice WSDL。
C# webservice 接口调用和开发 winform客户端发送默认格式L和json格式数据,服务端接收默认格式和json格式数据,自己做的测试,有不清楚的,可以看博文介绍,包括客户端和服务端,刚测试完,没问题,趁热发布
C#做的web service代码。不要嫌分高哟,因为里面包含的东西还是很多的:C#写的service程序,c# winform的客户端程序,asp.net的web客户端,还有就是基于axis的java console示例。
* 跨语言平台:WebService 接口调用可以跨语言平台,例如 C#、Java、Python 等。 * 跨操作系统:WebService 接口调用可以跨操作系统,例如 Windows、Linux、Unix 等。 * 松散耦合:WebService 接口调用可以实现松散...
4. 应用程序启动过程中的其他因素:除了web service本身,WinForm应用程序在首次运行时也可能需要初始化各种组件、资源加载等,这些都可能导致整体启动时间变长。 5. Web service的网络配置和硬件性能:Web service...
包括Windows编程基础、Visual Studio .NET IDE的使用、事件处理、SQL Server、XML、Ajax、JavaScript、ASP.NET、Oracle、RSS、计算机基础、HTML&JavaScript、面向对象编程、Java、数据库基础、JSP/Servlet、EJB/...
最最基础的调用webservice写的一个天气预报桌面程序,适合初学者学习。
ADO.NET是.NET Framework中的一个数据访问技术,它提供了一组用于连接数据库、执行查询、更新数据等功能的类。ADO.NET主要由以下几部分组成: - **Connection**:用于建立与数据库的连接。 - **Command**:用于执行...
- 面向对象编程是一种编程范式,它使用“对象”来设计软件。每个对象都可以包含数据(属性)和方法(行为)。面向对象编程的主要优点包括封装、继承和多态性。 - **Web服务与ASP.NET的关系**: - Web服务允许不同...