我必须承认,我喜欢 Windows? Update 功能。 在我的计算机开机的时间中,大约有 85% 的时间都连接到了 Internet 上,但是,像许多人一样,我当然不会这么多时间都在使用网络。 Windows XP 利用了这一未用的带宽,将网上可用的最新服务包和修补程序与我计算机上已安装的服务包和修补程序进行比较。 如果它找到了我需要的更新,便会在后台将它们下载下来。 完成后,Windows 会通知我计算机上有需要安装的新软件包。
如果可以选择的话,我希望让客户端上的每一个应用程序都像 Windows 一样允许自动更新。 现在有许多有利的条件以及现成的连接来实现这一功能。 如果要使应用程序自动进行自我更新,必须编写代码来处理发现、下载、安全性以及替换等方面的问题。
为了处理实际的下载,我将介绍 Windows 的一项新功能:后台智能传输服务 (BITS)。 在讨论此功能之后,我将介绍 .NET Framework 中可用来解决自动更新应用程序的安全性和替换问题的功能。
请注意,虽然 BITS 1.5 可重新发布软件可以很好地工作在 Windows 2000 和 Windows XP 上,但是 Microsoft 不打算在 Windows 9x 或 Windows Me 上支持 BITS API。 BITS 有望作为 Windows 的将来版本的一个组件提供。
但是,在开始下面的内容之前,需要指出的重要一点是:要使您的应用程序能够使用本文所介绍的技术,需要在 .NET 下对它进行管理。 BITS 自身是操作系统的一部分,而这里介绍的 .NET Framework 技术可以用于非托管的应用程序的自动更新功能。
困难
为了找到远程服务器上的更新,您的应用程序需要一种查询网络的手段。 这需要网络代码以及使应用程序和服务器可以用来彼此通信的简单协议。 我将在本文的后面继续探讨“发现”这个话题。
接下来您还必须能够下载软件包。 由于 .NET 在网络方面驾轻就熟,因此下载可能看上去不像是一个大问题,但是下载用户所请求的文件仅仅是一个方面。另一个方面是在未经用户同意的情况下下载大型文件。 礼貌的自动更新应用程序仅仅使用残留的带宽来下载更新。 这听起来很不错,但是,您会看到,它带来了一个相当困难的技术问题。 幸运的是,相应的解决方案已存在。
安全可能是首当其冲的一个问题。 请花一点时间想一想 Windows Update 功能。 它的一个主要目的是获取安全修补程序。 想想,如果 Windows Update 自身都不能确认它安装的是否是安全的代码,情况会怎么样? 显而易见,从 Internet 上下载代码并执行该代码的任何应用程序都必须将安全性作为一个首要问题。 因此,我将探讨如何确保自动更新应用程序的安全性。
最后一个需要考虑的因素是使用应用程序自身的新版本来替换应用程序的过程。 此问题之所以引人关注,是因为它要求代码在将自身从系统中删除时能够运行。 处理这一技巧有许多种方法。
关于这些困难,最庆幸的一件事情是在 .NET Framework 与 Windows 之间,所有工具都是现成的,可以立刻用来解决这些问题。
BITS 基础知识
BITS 是 Windows 中新增的一项非常实用的文件传输功能,它通过 HTTP 异步地从远程服务器上下载文件。 BITS 可以专门利用空闲带宽来处理多个用户的多个下载任务。 尽管 BITS 不仅仅限于自动更新应用程序使用,但它是 Windows Update 使用的基础 API。 并且,由于它可用于任何应用程序,因此可用来完成创建自动更新应用程序的过程中所涉及到的大部分非常困难的工作。
下面是基本思想。 应用程序请求 BITS 处理一个或一组文件的下载。 BITS 将该任务添加到自己的队列中,并将该任务与应用程序所运行在的用户上下文关联。 只要用户登录,BITS 就会利用空闲带宽在网络中细细搜寻 (Drizzle) 这些文件。 事实上,BITS 技术的代号名称就是 Drizzle,这个词被证明是 BITS 所执行的工作的一个非常恰当的描述。
那么它到底是怎样工作的呢? 这项技术实际是一项非常复杂的技术。 首先,BITS 是作为一个 Windows 服务实现的,它维护组织成一组优先级队列的任务集合。 优先级包括前台、高、普通和低。 它按照循环法的原则,通过大约五分钟的时间片为同一优先级的每个任务分配带宽。 一旦队列中没有剩余的任务,便立即检查下一个优先级队列中的任务。
前台队列中的任务使用尽可能多的网络带宽,由于这一原因,前台优先级应只供响应用户请求的代码使用。 余下的优先级(高、普通和低)远远比前台优先级更引人关注,因为它们全都是后台优先级,也就是说,它们仅仅使用未用的网络带宽。
为了实现此后台功能,BITS 监视网络数据包,并忽略不属于它的数据包。 余下的数据包被认为是计算机带宽上的活动负载。 BITS 使用活动负载信息以及连接速度和其他某些统计信息来确定应继续下载文件还是暂停(以增加活动用户的吞吐量)。 因此,用户不会遇到带宽问题。
能够立即中断所做的工作对于 BITS 是非常重要的。 在许多情况下,BITS 不得不在仅仅下载了文件的一部分的情况下放弃与网络的连接甚至完全断开连接。 但是,下载了一部分的文件会保存起来,这样,当 BITS 抓住了与网络连接的片刻机会时,便能够从中断的位置继续下载。 这种恢复功能也有一些副作用。
请记住 BITS 是用于从 HTTP 服务器传输文件的。 要使 BITS 能够工作,服务器应与 HTTP 1.1 兼容,或者至少支持 GET 方法中的 Range 头。 这是因为 BITS 需要能够请求文件的一部分。 此外,下载的内容必须是静态内容,例如,标记文件、代码文件、位图或声音。 当请求动态内容(例如,CGI、ISAPI 或 ASP.NET 产生的内容)时,包含 Range 头的 GET 请求没有意义。
当前,BITS 有两个版本: 1.0 和 1.5。 BITS 1.0 随同 Windows XP 一起提供,它具有下列功能: 可与对话框及其他 UI 元素一起使用的可中断后台文件下载、下载优先级、任务完成及出错时的可选通知以及可选的进度通知。 BITS 1.5 随同 Windows .NET Server 一起提供。 除了 BITS 1.0 中包含的功能外,1.5 版还具有可中断的后台文件上载以及使用“基本”、“摘要”、“NTLM”、“协商”(Kerberos) 或“Passport”对连接进行身份验证的功能。 BITS 1.5 还作为与 Windows 2000 及更高版本兼容的可重新发布软件提供(请参阅后台智能传输服务)。
BITS 1.0 中的全部功能对于编写自动更新应用程序已够用,但是使用 BITS 1.5 功能可以执行更复杂的任务,例如,出售更新或处理应用程序与服务器之间的交互。
BITS、COM 和托管代码
BITS API 是作为 COM 对象实现的,正因为此,所以不能编写 .NET Framework 版的 API。 幸运的是,BITS API 非常简单易用。 本文所包含的示例应用程序是使用 C# 编写的,因此为了使用 BITS API,我不得不硬着头皮使用 .NET Framework 的 COM Interop 功能。 我在这里不深入探讨运行库可调用包装 (RCW) 及相关的内容,而仅仅说明我使用此 API 所遵循的过程。
如果我使用的是 C++,那么我的 BITS 代码的开头部分可能如下所示:
IBackgroundCopyManager* pBCM = NULL;
hr = CoCreateInstance(__uuidof(BackgroundCopyManager), NULL,
CLSCTX_LOCAL_SERVER, __uuidof(IBackgroundCopyManager),
(void**) &pBCM);
if (SUCCEEDED(hr)) {
// Party-on with the pBCM interface pointer
}
等效的 C# 代码使用 new 关键字创建 BackgroundCopyManager 对象,然后通过转换获得对 IBackgroundCopyManager 接口的引用,而不是通过调用像 CoCreateInstance 或 QueryInterface 这样的方法。 下面的代码片段是 C# 示例,它的作用是获取 IBackgroundCopyManager 接口:
IBackgroundCopyManager bcm = null;
// Create BITS object
bcm = (IBackgroundCopyManager)new BackgroundCopyManager();
此代码的简单性有一点误导的作用,因为需要做更多的工作来分别将托管的 BackgroundCopyManager 和 IBackgroundCopyManager 类型与底层的 COM 对象和接口关联。 .NET Framework 通过 RCW 来处理与 COM 对象的交互。 要将托管类型与 RCW 关联,必须使用属性。 图 1 中的代码说明了如何声明 BackgroundCopyManager 类和 IBackgroundCopyManager 接口,以使它们表示 BITS COM 对象。
图 1 中的代码有很多的属性(ComImportAttribute、GuidAttribute、MarshalAsAttribute 等等),这使真正的代码看起来不明显。 事实上,没有真正的代码。 接口和类的定义实际上仅仅是元数据形式的封送占位符,公共语言运行库 (CLR) 用它来直接调用 BITS COM API。
代码示例(请见本文顶部的链接)所包含的 InteropBits.cs 文件提供了与 BITS API 一起使用的所有接口、枚举类型及结构的 C# Interop 代码。 尽管示例仅仅使用了几个方法,但是我提供了完整的实现,因为它有助于您探索此 API 的更多功能。 值得注意的一点是,InteropBits.cs 文件中的代码不是托管 API,而是 COM API 实现之上的 Interop API。 Microsoft 终有一天会发布通过与 .NET Framework 类库的其余部分一致的方法向 .NET Framework 代码公开 BITS 功能的 API。
图 1 中的代码以及 InteropBits.cs 示例文件中的完整实现是手动创建的。 .NET Framework SDK 自带一个名为 TlbImp.exe 的工具。如果您有一个描述所针对的 COM API 的 TLB 文件,可以使用此工具创建类似的代码,并将其编译为托管程序集。 BITS API 不自带 TLB 文件,但是,Platform SDK 中包含一个名为 Bits.idl 的接口定义文件,该文件