posts - 97, comments - 5, trackbacks - 0, articles - 19
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理
摘要

  .net框架组件和Windows有一些有趣的API,它们能够建立通过网络自动更新的应用程序。像Windows一样把应用程序编写为自动更新有很多好处,包括方便了用户,减轻网络管理员的维护工作量。自动更新需要注意一些因素,例如发现、安全和文件更替。本文使用了BITS API和一些.NET框架组件的特性,使应用程序可以像Windows一样自动更新。

  我喜欢Windows更新特性。我的计算机开启后,85%的时间连接着Internet,象很多人一样,我并没有那么多时间使用网络。Windows XP利用未使用的带宽来比较网络上可用的最新服务包和补丁与本机安装的补丁包,如果发现需要更新,就在后台下载它们,下载完成后它提示有新的内容需要安装。

  如果我有选择,客户端的每个应用程序都应该允许自动更新。如果你要应用程序自动更新,必须编写代码来处理发现、下载、安全和替换。

  为了处理实际的下载,我将使用Windows的后台智能传输服务(Background Intelligent Transfer Service,BITS)特性。我将使用.NET框架组件的特性来解决自动更新应用程序的安全和更新问题。

  困难

  为了查找远程服务器上的更新,应用程序必须有查询网络的途径,这需要网络编程、简单的应用程序与服务器通讯的协议。这将在后面的"发现"节中讲到。

  下一步是下载。下载看起来不需要考虑联网的问题,但要考虑下载用户请求的文件,以及在没有用户同意时下载大文件。友好的自动更新应用程序将使用剩余的带宽下载更新。这听起来简单,但却是一个技术难题,幸运的是已经有了解决方法。

  安全也许是最关键的考虑因素。考虑一下Windows更新特性,它的主要目的是获取安全补丁,想象一下如果Windows更新本身不能验证是否安装了安全的代码。很明显任何从Internet下载并执行代码的应用程序必须有最高的安全级。因此我将讨论怎样使自动更新的应用程序更安全。

  最后的考虑因素是使用新版应用程序更换原应用程序的过程。这个问题比较有趣,因为它要求代码运行时将自己从系统删除,有多种办法可以实现该功能。

  BITS基础

  BITS是一个新的Windows文件传输特性,它通过HTTP异步从远程服务器下载文件。BITS可以使用专门的空闲带宽管理多个用户的多个下载。尽管BITS的使用不限于自动更新应用程序,但是它是Windows更新使用的低层API。由于它对于任何应用程序都是可用的,因而用于做许多实际的工作,包括建立自动更新的应用程序。
以下是基本的想法。应用程序请求BITS管理文件的下载。BITS将工作添加到它的队列并将它与应用程序运行的用户环境关联。一旦用户登录,BITS就使用空闲带宽通过网络慢慢下载文件。实际上BITS技术的代码名称是Drizzle,它描述BITS做什么。

  这是怎么实现的呢?这项技术相当复杂。首先,BITS的实现方式与维护按优先级(前台、高、正常、低)队列组织的工作集的Windows服务一样。相同优先级的工作按时间片给定五分钟带宽。队列中一旦没有工作了,就检查下一优先级队列的工作。

  前台队列中的工作使用尽可能大的网络带宽,由于这个原因前台优先级只用于响应用户请求的代码。其它的优先级--高、正常和低--都是后台优先级,它们只利用空闲的网络带宽。

  为了获得后台特性,BITS监视网络数据包,并不处理自己的包。剩余的包用于计算带宽的活动负载。BITS利用活动负载信息与连接速度和一些静态信息来决定是否继续下载文件,或者为了提高活动用户的流量停止。由于这个原因,用户不会遭遇带宽问题。

  对于BITS来说一旦注意到就停止工作的能力非常重要。在很多情况下BITS在下载了文件的一部分后就要放弃网络,甚至连接也一起丢失了。文件下载的部分被保存了,但是当BITS再次使用网络时它从断点开始。恢复的能力是有效果的。

  BITS用于从HTTP服务器传输文件。服务器必须与HTTP 1.1兼容,或者至少支持在GET方法中包含Range头,这是因为BITS需要请求文件的一部分。此外,下载的内容必须是静态内容,例如标记文件、代码文件、位图或声音。包含Range头的请求在请求动态内容如CGI、ISAPI或ASP.NET时不做任何操作。

  目前BITS有两种版本:1.0和1.5。BITS 1.0随Windows XP发布,有以下特性:可中断的文件下载、下载优先级、可选择的工作完成通知和错误情况、可选择使用对话框或者其它UI元素进行过程通知。BITS 1.5 与Windows .NET Server一起发布,除了有BITS 1.0的特性外,还有可中断的文件上载以及使用Basic、 Digest、 NTLM、 Negotiate(Kerberos) 或Passport认证连接,它与Windows 2000以上版本兼容。


 BITS、 COM和可管理代码

  BITS API是作为COM对象实现的,到目前为止还没有.net框架组件版本的API,幸运的是BITS API很直接并易于使用。本文的例程用C#编写,如果使用C++,BITS代码要使用下面的代码开始:

 

IBackgroundCopyManager* PBCM = NULL;

hr = CoCreateInstance(__uuidof(BackgroundCopyManager), NULL,
CLSCTX_LOCAL_SERVER, __uuidof(IBackgroundCopyManager),
(void**) &pBCM);
if (SUCCEEDED(hr)) {
// 使用pBCM接口指针
}

  C#使用new关键字等效地建立BackgroundCopyManager对象,接着通过计算获取一个IBackgroundCopyManager接口的引用,而不采用调用CoCreateInstance或者 QueryInterface等方法。下面的代码获取了IBackgroundCopyManager接口:

IBackgroundCopyManager bcm = null;
//建立BITS对象
bcm = (IBackgroundCopyManager)new BackgroundCopyManager();

  该代码很简单,可能使人误解,因为还需要做很多工作要做,要将可管理的BackgroundCopyManager和IBackgroundCopyManager类型与下层COM对象和接口分别对应地联系起来。.NET框架组件通过RCW管理与COM对象的交互操作。要使用RCW关联一个可管理的类型,你必须使用属性。图1中的代码显示了怎样声明BackgroundCopyManager类和IBackgroundCopyManager接口,这样它们就描述了BITS COM对象。

[GuidAttribute("4991D34B-80A1-4291-83B6-3328366B9097")]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
[ComImportAttribute()]
class BackgroundCopyManager{}

[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
[GuidAttribute("5CE34C0D-0DC9-4C1F-897C-DAA1B78CEE7C")]
[ComImportAttribute()]
interface IBackgroundCopyManager {
void CreateJob(
[MarshalAs(UnmanagedType.LPWStr)] string DisplayName,
BG_JOB_TYPE Type, out Guid pJobId,
[MarshalAs(UnmanagedType.Interface)]
out IBackgroundCopyJob ppJob);

void GetJob(ref Guid jobID,
[MarshalAs(UnmanagedType.Interface)]
out IBackgroundCopyJob ppJob);

void EnumJobs(uint dwFlags,
[MarshalAs(UnmanagedType.Interface)]
out IEnumBackgroundCopyJobs ppenum);

void GetErrorDescription(
[MarshalAs(UnmanagedType.Error)] int hResult, uint LanguageId,
[MarshalAs(UnmanagedType.LPWStr)]
out string pErrorDescription);
}

图1.定义RCW类型

  图1中的代码属性(ComImportAttribute、 GuidAttribute、MarshalAsAttribute等等)很难以理解,以至于实际代码看起来不清楚。实际上这不是真实的代码。在通用语言运行时(CLR)调用的元数据形式中接口和类定义仅仅是一些排列整齐的占位符。

  代码例子中的InteropBits.cs文件为C#提供了与BITS API使用所有接口、枚举类型和结构体的交互操作代码。虽然例程只使用了少数几个方法,但是我也包含了完整的实现,因为如果你决定研究API的更多特性会发现这是有用的。InteropBits.cs文件中的代码不是可管理(managed)的API,因此它没有什么价值,但它是通过COM API与API实现了交互操作。最后,预计微软将发放一个暴露BITS功能到.NET框架组件代码的API,这样才能与其它的.NET框架组件的类库一致。

  图1中代码文件InteropBits.cs是手工建立的。.NET框架组件SDK发布了一个叫TlbImp.exe的工具,它建立和编译相似的代码成为一个可管理的组件,假如你有一个描述目标COM API的TLB文件的话。BITS API没有与TLB文件一起发布,但是平台SDK包括一个定义接口的叫Bits.idl的文件描述该接口。

  我使用MIDL.exe工具(与平台SDK一起发布)从Bits.idl中建立TLB文件。接着我使用TlbImp.exe建立一个描述该TLB文件的可管理组件。我使用ILDasm.exe将TlbImp.exe产生的组件分解为中间语言,最后我使用中间语言为向导建立C#代码,调整它的位置使它更有用、更正确。这种COM与C#交互操作的途径诚然乏味,但是它提供了对最后输出的例外控程度。
在自动更新应用程序中使用BITS

  BITS服务在工作期间管理文件下载。一个应用程序建立传送工作,接着给该工作添加一个或者多个文件。一旦工作的文件列表确定了,任务就继续开始(因为开始时工作的状态是挂起的)。工作用于管理一些细节,例如优先级、认证和错误管理。在任何时候工作都能被应用程序终止。

  一旦BITS在一个工作中完成了所有文件的传输,应用程序就调用一个方法结束该工作。Complete方法复制所有文件到它们的最后目的地。尽管BITS文档指导使用"正在复制(copying)"文件结束任务,但是现实中是在目的地将建立临时隐藏文件,Complete方法简单地更改隐藏文件的名称并使它们可见。

  即使最小使用BITS API(只使用IBackgroundCopyManager和IBackgroundCopyJob接口)也能得到自动更新应用程序所需要的每个文件。如果需要使用其它特性,例如枚举或者完成、错误通知,你必须使用另外的接口。

  例程AutoUpdater.exe使用BITS同时完成更新的发现和下载。为了到这个目的,应用程序为更新定义了连续的名称模式:Update1.dll、Update2.dll等等。尽管这不是发现更新的唯一途径,但是它使BITS功能工作得很好。例程维护了一个XML文件用于保存更新状态,该文件的两个相关的部分是接下来的更新数量和描述当前BITS下载工作的GUID(如果目前没有工作的话)。

  应用程序每次运行,它打开XML文件并检查它看接下来的更新是否已经下载。如果没有的话,XML文件中就没有工作的GUID,应用程序初始化BITS工作来在线下载接下来的更新。下面的代码演示了初始化下载工作的必要方法:

 

IBackgroundCopyJob job=null;

// 建立工作下载接下来的更新
bcm.CreateJob("Application Update",
BG_JOB_TYPE.BG_JOB_TYPE_DOWNLOAD, out jobID, out job);

// 将工作添加到文件
job.AddFile(updateUrl, localLocation);
job.Resume(); // 启动工作

  调用job.AddFile传递要下载文件的位置、完成的本地路径和最后保存的文件名。注意任务在开始为挂起状态,在BITS服务处理工作前必须重新开始。

  如果XML文件中有GUID,BITS任务就已经被初始化了。因此该GUID被传递到IBackgroundCopyManager.GetJob方法查看先前安排的下载任务是否有结果了。下一步依赖工作的状态。
在下面的代码(图2)中如果工作处于错误状态,应用程序就结束该工作(它从队列中清除失败工作)并接着为相同的下载文件建立一个新工作。错误状态的最可能的原因很简单,即有名称不存在的更新。这实现了例程中的发现更新部分。

  使用BITS查询更新的好处是BITS在后台工作。因此尽管查询并不是特别的优雅,非强制性的BITS下载是发现更新的可行方法。

  如果处于已传输状态,应用程序接着调用IBackgroundCopyJob.Complete来结束任务。这导致文件写入目标目录,使它准备好更新。接着应用程序返回,这时下载的文件片会融合在一起。

bcm.GetJob(ref jobID, out job); //获取BITS工作对象
job.GetState(out state); // 检查状态

switch(state){
case BG_JOB_STATE.BG_JOB_STATE_ERROR: //如果出现错误
job.Complete();
xml.BitsJob = Guid.Empty;
Marshal.ReleaseComObject(job);
job = null;
break; //继续建立新工作
case BG_JOB_STATE.BG_JOB_STATE_TRANSFERRED: //如果获得了文件
job.Complete(); //结束工作
xml.BitsJob = Guid.Empty;
return; //所有完成,返回
default:
return;
}
... // 为更新下载初始化一个新工作

图2.检查工作状态和结束工作

  最后,在默认情况下图2种的例程简单地返回并忽略工作。工作可能有两种状态:正在传输状态(这意味者工作在进行中)或者瞬间错误状态。这两种状态都被认为是可以潜在成功的,因此应用程序单独留下足够空间。如果BITS任务进入瞬时错误状态,BITS服务认为该错误可以恢复,因此它再次尝试。最后,瞬时错误的任务要么成功要么BITS将它置为错误状态。

  错误恢复逻辑覆盖了整个发现和下载AutoUpdater.exe应用程序组件。鉴于下载过程的更深的功能,该实现方法看起来不像网络通讯并且与通常的文件复制相似。在产品应用程序中你会发现将使用BITS API的大量其它特性的使用,例如自动传输通知、任务优先级维护和上载任务。
BITS的一些考虑因素

  BITS设计为从不初始化网络连接。这样的设计最好,因为你不希望无需连接的应用程序仅仅为了检查更新而拨号到一个ISP。但是BITS 1.0和1.5受收到Internet连接共享限制的影响。如果系统A共享系统B的Internet连接,在系统A上的初始化BITS工作的应用程序可能引起系统B初始化拨号,这是因为当前BITS服务的实现没有考虑共享连接的问题。

  这是一个难以解决的问题,但是可能在BITS的未来版本中会有一个补丁。Windows更新特性也有这个问题,使用BITS的应用程序在初始化共享连接的拨号与Windows没有什么区别。

  BITS的最后一个问题是工作文件的一致性。如果你下载的工作包含多个文件,BITS不认为工作完成了,也不认为文件到达了它们的最后目的地,直到所有文件都可用。但是,BITS无法知道在其它文件已经下载后,服务器上的一个文件改变了。因为这个原因,服务器上的更新可能影响客户端下载的一致性。

  有两种方法解决该问题。第一种是限制BITS下载为单个文件的工作,这是我在例程中使用的方法。第二种方法是保存单个工作的所有文件到服务器的同一个目录中。当对这些文件有更新时,在服务器上为新文件集合建立一个新目录。使用这种方法,在原来的工作上的传输中的客户端不会受到新文件集合的影响,这种解决方法的问题是服务端的文件定位改变了,客户端需要一条途径来获取新文件的位置。

  如果你需要查找BITS代码,BitsAdmin.exe是一个最好的帮手。该工具随Windows XP光盘分发,在Support\Tools子目录下。为了节省时间,我没有安装该工具,而是直接将.exe从Support.cab文件中解压出来。

  当你第一次熟悉BITS下载时,BitsAdmin.exe工具非常有用。几乎该API的所有特性都通过这个命令行工具的选项暴露了。你能枚举工作、检查工作状态、查看详细的工作和错误信息;你能初始化、挂起、继续和结束下载工作;最后,你能改变工作的优先级并从BitsAdmin.exe中取消工作。

  Windows API通过相关工具能够像这样访问的情况很少。实际上,脚本(例如批处理文件)可以通过BitsAdmin.exe工具完整地使用BITS。BITS是发现和下载更新的一个强大的解决方案,对于大多数应用程序来说,它的缺陷很少。现在我们看看安全性问题。




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1082301




相关文章:

相关链接: