(原创文章,转载请注明来源:http://blog.csdn.net/hulihui)
0 前言 |
>>[前言]、[第1节]、[第2节]、[第3节]、[第4节]、[第5节] |
众所周知,诸如ASP.NET等的Internet应用系统易于发布与更新版本——只需要修改或增加服务器端程序。比较而言,通过光盘或安装程序发布的窗体应用程序或客户端系统,版本升级则要困难得多。为此,Microsoft在其.NET平台上提供了ClickOnce技术,该技术具有启动前更新或启动后更新(下次运行时安装)两种模式,但使用时客户端需要证书,也不能在下载前做文件压缩处理,不可指定客户端文件安装路径,主要针对.NET程序集,不能发布部署其他类型的文件(如数据库文件、非程序集文件)。显然,ClickOnce技术缺乏应用灵活性。
本文介绍的基于.NET WebService的可扩展客户端自升级框架WebSAUF(Web Smart Auto Upgrade Framework),具有如下功能和特性:
本文内容如下:
1 技术思路与架构 |
>>[前言]、[第1节]、[第2节]、[第3节]、[第4节]、[第5节] |
客户端通过WebService请求Web服务器升级文件清单,然后在客户端比较本地的指定文件夹的同名文件,确定需要升级的文件名,接着下载这些升级文件并改名存储到同文件夹中,最后启动命令行bat进程、终止当前进程完成最后的删除原文件与改名升级文件操作。
从应用或功能划分,WebSAUF框架有四组类:服务器端类(含一个升级文件配置清单文件)、客户端类、压缩方法类与辅助类,它们之间的关系见下图:
2 关键实现技术 |
>>[前言]、[第1节]、[第2节]、[第3节]、[第4节]、[第5节] |
WebSAUF关键实现技术包括:框架可扩展性设计、GZipStream压缩处理技巧、WebService代理类重构、命令行自升级方法、程序集或文件版本获取等。
WebSAUF可扩展性主要体现在文件压缩处理方法上,即文件下载前是否做压缩/解压缩操作,关键是定制TCompressMethod类型,通过如下两种设计达到目的:
.NET提供了GZipStream/DeflateStream压缩组件,与直接压缩文件到磁盘上的方法不同,把文件压缩到一个字节数组的处理技巧是:必须关闭GZipStream/DeflateStream压缩流,才能获得正确的压缩后文件字节数组:
protected virtual byte[] ReadFileByGZipCompress(string fileFullName) { byte[] fileContent; using (FileStream srcStream = new FileStream(fileFullName, FileMode.Open, FileAccess.Read)) using (MemoryStream memStream = new MemoryStream()) { using (GZipStream zipStream = new GZipStream(memStream, CompressionMode.Compress, true)) { int readCount = m_bufferSize; // 读缓冲区m_buffer大小 = 8K while (readCount == m_bufferSize) { readCount = srcStream.Read(m_buffer, 0, m_bufferSize); if (readCount > 0) { zipStream.Write(m_buffer, 0, readCount); } } } memStream.Position = 0; fileContent = new byte[memStream.Length]; memStream.Read(fileContent, 0, fileContent.Length); // 读内存流到字节数组中 } return fileContent; }
上述代码的关键点为:第一,GZipStream构造函数的第三个参数leaveOpen必须为true(默认为false);第二,必须先关闭GZipStream,再读关联的内存流的数据。事实上,因为leaveOpen=true,此时的压缩流仍然没有关闭,Close只做了一个结束压缩的操作而已。
下面代码段将获得错误的文件字节数组,因为GZipStream没有关闭,却通过MemoryStream读压缩流字节数据:
protected virtual byte[] ReadFileByGZipCompress(string fileFullName) { byte[] fileContent; using (FileStream srcStream = new FileStream(fileFullName, FileMode.Open, FileAccess.Read)) using (MemoryStream memStream = new MemoryStream()) using (GZipStream zipStream = new GZipStream(memStream, CompressionMode.Compress, true)) { // 代码 } return fileContent; }
下面代码段将抛出异常“无法访问已关闭的流”,因为leaveOpen为false:
protected virtual byte[] ReadFileByGZipCompress(string fileFullName) { byte[] fileContent; using (FileStream srcStream = new FileStream(fileFullName, FileMode.Open, FileAccess.Read)) using (MemoryStream memStream = new MemoryStream()) { using (GZipStream zipStream = new GZipStream(memStream, CompressionMode.Compress)) { // 代码 } // 代码 } return fileContent; }
WebService客户端调用的是SoapHttpClientProtocol派生的、与服务器端WebService网页asmx文件相关的一个代理类。VS2005开发工具提供了一个便利的获取该代理类的方法,但产生代理类时包含了相关的辅助类型、方法及异步方法、方法事件委托等。根据需要,可以删除辅助类,简化代理类代码,更改代理类名和文件名,等等:
经过上述修改,TSmartAutoUpgradeWebServiceProxy是一个独立的类型,可以直接在客户端程序中使用。但必须指出如下几点:
显然,进程未终结前是不能删除或替换当前可执行文件(exe程序文件)的。WebSAUF采用Windows的命令行处理方式,构建一个命令行bat文件,退出当前进程前执行bat,完成文件升级工作。主要步骤包括:别名保存下载的升级文件为,创建bat文件,终止当前进程前调用bat,该bat做循环操作——删除客户端升级原文件、更改别名为原文件名。下面代码是一个实际的bat文件,随后给出的是TSmartAutoUpgradeClient类中产生bat文件的c#代码。
@echo off D: cd/ cd D:/WebSAUF 1.0/TestClient/bin/ if not exist TestClient.exe goto loop1001 :loop1 attrib -a -r -s -h TestClient.exe del TestClient.exe if exist TestClient.exe goto loop1 :loop1001 ren TestClient.exe.AutoUpgrade TestClient.exe D: cd/ cd D:/WebSAUF 1.0/TestClient/bin/sub2/ if not exist WinExeByDelphi.exe goto loop1002 :loop2 attrib -a -r -s -h WinExeByDelphi.exe del WinExeByDelphi.exe if exist WinExeByDelphi.exe goto loop2 :loop1002 ren WinExeByDelphi.exe.AutoUpgrade WinExeByDelphi.exe private void UpgradeByBatchKind(TUpgradeFileInfoCollection upgradeFileContents) { string batFile = this.ProgramStartupPath + m_upgradeBatFileName using (StreamWriter bat = new StreamWriter(batFile, false, Encoding.Default)) // 处理汉字时要编码格式 { bat.WriteLine(@"@echo off"); int k = 1; foreach (TUpgradeFileInfo upgradeFileInfo in upgradeFileContents.UpgradeFileInfoCollection) { string filePath = this.GetClientFileFullPath(upgradeFileInfo.Subdirectory); // 同时创建文件夹 bat.WriteLine(filePath.Substring(0, 1) + ":"); bat.WriteLine(@"cd/"); bat.WriteLine("cd " + filePath); string fileName = upgradeFileInfo.FileName; // 在当前路径下操作 string upgradeFileName = upgradeFileInfo.FileName + m_tempFileSuffix; bat.WriteLine(@"if not exist " + fileName + " goto loop" + (1000 + k).ToString()); // 循环 bat.WriteLine(@":loop" + k.ToString()); bat.WriteLine(@"attrib -a -r -s -h " + fileName); bat.WriteLine(@"del " + fileName); bat.WriteLine(@"if exist " + fileName + " goto loop" + k.ToString()); // 循环 bat.WriteLine(@":loop" + (1000 + k).ToString()); bat.WriteLine(@"ren " + upgradeFileName + " " + fileName); // 将下载的升级文件更名 k++; } bat.WriteLine(@"del %0"); // 删除批处理自身 } ProcessStartInfo info = new ProcessStartInfo(batFile); // 启动自删除批处理文件 info.WindowStyle = ProcessWindowStyle.Hidden; Process.Start(info); Environment.Exit(0); // 强制关闭当前进程 }
上述命令行处理代码的关键有四点:1)建立一个命令行处理进程,注意需要处理汉字目录名;2)启动批处理进程前终止当前进程,即Process.Start(info)、Environment.Exit(0);3)在文件所在的文件夹中做检测、删除与改名操作;4)循环删除文件并更改别名为原名。
Windowns应用程序(exe/dll)均有文件版本,此外.NET程序集还有所谓程序集版本,它们均可以用来识别更新的程序。WebSAUF首先判断配置清单文件是否有程序集版本,否则判断是否有文件版本,见如下代码:
private void SetVersionInfo(string fileFullName) { try { Version ver = System.Reflection.AssemblyName.GetAssemblyName(fileFullName).Version; m_assemblyVersion = ver.ToString(); } catch (BadImageFormatException) // 非.NET程序集 { try { FileVersionInfo verInfo = FileVersionInfo.GetVersionInfo(fileFullName); m_fileVersion = verInfo.FileVersion; } catch { } } }
3 使用WebSAUF框架 |
>>[前言]、[第1节]、[第2节]、[第3节]、[第4节]、[第5节] |
<%@ WebService Language="c#" Codebehind = "TestWebSAUF_WebService.asmx.cs" Class = "CSUST.NET.TestWebSAUF_WebService" %>其中,Codebehind属性值为asmx网页代码cs文件,Class属性值为TSmartAutoUpgradeWebService类或其派生类的类型全名(名称空间+类型名)。
需要指出,如果没有特定的压缩算法等定制行为,只需要做上面的第1步和第4步工作。
配置文件具有根元素<FileList>,包含若干<File>子元素,该子元素有四个属性:Name、UpgradeKind、CompressKind与Subdirectory。除Name外,其它三个属性可以省略,此时取其默认配置值。
下面是示例代码中的一个配置文件内容:
<?xml version="1.0" encoding="utf-8"?> <FileList> <File Name = "/UpgradeFiles/TestClient.exe" CompressKind = "GZip" Subdirectory = ""/> <File Name = "/UpgradeFiles/ClassDllByVS2005.dll" UpgradeKind = "Version" CompressKind = "None"/> <File Name = "/UpgradeFiles/WinExeByDelphi.exe" UpgradeKind = "Override" CompressKind = "Custom" Subdirectory = "sub2/" /> <File Name = "/UpgradeFiles/WinDllByDelphi.dll" UpgradeKind = "NewFile" CompressKind = "None" Subdirectory = "/sub3/sub33" /> <File Name = "/UpgradeFiles/UpgradeKind_NewFile.xls" UpgradeKind = "NewFile" CompressKind = "Deflate" /> <File Name = "/UpgradeFiles/UpgradeKind_Override.mdb" UpgradeKind = "Override" CompressKind = "Custom" /> </FileList>
配置文件SmartAutoUpgradeFileConfig默认保存在WebService同文件夹中,可以在Web.Config中的AppSettings节指定存放文件夹和文件名,见如下示例代码:
<appSettings> <add key = "SmartAutoUpgradeFileConfig" value = "/Upgrade/SmartAutoUpgradeFileConfig.xml" /> </appSettings>
泛型类TSmartAutoUpgradeClient可以应用于窗体或控制台等应用系统中,它提供了WebSAUF客户端全部的对外接口:属性、方法和事件。
有两个重载版本,一个是默认构造函数,一个包含TSmartAutoUpgradeWebServiceProxy对象参数,见如下代码:
public TSmartAutoUpgradeClient() { this.CreateUpgradeHistoryListFile(); } public TSmartAutoUpgradeClient(TSmartAutoUpgradeWebServiceProxy upgradeWebService) { m_upgradeWebService = upgradeWebService; this.CreateUpgradeHistoryListFile(); }
构造函数中的私有方法CreateUpgradeHistoryListFile()用于检测或创建一个升级文件历史清单AutoUpgradeFileHistory.xml,该文件保存在客户端程序文件夹中。下面给出一个升级历史清单文件的内容:
<?xml version="1.0" encoding="utf-8"?> <FileList CreateDate="2009-1-23 11:45:50"> <File UpgradeDate="2009-01-23 11:50:13" Name="TestClient.exe" UpgradeKind="Version" Subdirectory="" /> <File UpgradeDate="2009-01-23 11:50:13" Name="ClassDllByVS2005.dll" UpgradeKind="Version" Subdirectory="" /> <File UpgradeDate="2009-01-23 11:50:13" Name="WinExeByDelphi.exe" UpgradeKind="Override" Subdirectory="sub2" /> <File UpgradeDate="2009-01-23 11:50:13" Name="WinDllByDelphi.dll" UpgradeKind="NewFile" Subdirectory="/sub3/sub33" /> <File UpgradeDate="2009-01-23 11:50:13" Name="UpgradeKind_NewFile.xls" UpgradeKind="NewFile" Subdirectory="" /> <File UpgradeDate="2009-01-23 11:50:13" Name="UpgradeKind_Override.mdb" UpgradeKind="Override" Subdirectory="" /> </FileList>
类型TSmartAutoUpgradeClient的事件都使用EventHandler<TFileUpgradeEventArgs>作为事件处理委托,事件参数类型TFileUpgradeEventArgs具有如下属性:
下载zip文件中包含WebSAUF框架源码和WebService及客户端Demo,解决方案文件为WebSAUF.sln,包含两个项目:TestWebServcie项目和TestClient项目。解压缩zip文件后,将产生如下三个文件夹:
安装一个Web服务器(笔者使用XP自带的IIS)以及.NET Framework 2.0及以后版本,运行下载包示例程序的步骤如下:
CSUST.NET.TSmartAutoUpgradeWebServiceProxy webService = new CSUST.NET.TSmartAutoUpgradeWebServiceProxy(); webService.Url = "http://172.30.141.16/WebSAUF/TestWebSAUF_WebService.asmx";
4 总结与展望 |
>>[前言]、[第1节]、[第2节]、[第3节]、[第4节]、[第5节] |
本文介绍的WebSAUF框架的思路来源于笔者研制的”公路交通流量调查数据综合处理系统“项目,该项目应用了WebService技术,但只使用了WebSAUF框架中的命令行程序代码,且仅更新客户端exe程序本身。全省数十家机构应用表明,采用自升级客户端技术可以提高开发效率、节省发布与维护成本。除了因网络影响到升级速度外,可以与ASP.NeT媲美,当然具有ASP.NET无法拥有的丰富客户端体验。
在2008年,同教研室的两位老师各自研制了一个WebService应用系统,大家在一起经常探讨WebService开发与应用技术,一致认为,如果能像ASP.NET那样方便发布与维护程序集或文件,那么WebService的解决方案将有更大的应用需求。因此,笔者强烈地意思到有必要构建一个通用的客户端程序升级框架,满足客户端自升级需求,突破客户端应用系统自升级技术障碍。
牛年的头一个月,在笔者紧张的“交通综合历史数据库“项目的设计与实现过程中,停顿两周时间,构思、设计与完成了WebSAUF1.0版,框架代码约1500行(日均200行),使用了StarUML建模工具和VS2005开发工具。控制台和窗体客户端程序的测试表明,WebSAUF框架基本达到设计要求:自升级、可配置、独立性与可扩展性。但有一些技术问题尚待解决,例如:WebService登录安全性、WebService交互Ticket票据应用方式、代理类的简化程度等等,因时间关系,只有留待以后的实际应用中进一步修改完善。欢迎感兴趣者测试、使用、评论或建议WebSAUF框架。
5 版本与源码 |
>>[前言]、[第1节]、[第2节]、[第3节]、[第4节]、[第5节] |