概述

随着时间的推移,已经形成这样一种惯例:即将应用程序构建成一组组件,分布于计算机网络之间,并作为整个程序的一部分一起运行。过去,分布式应用程序逻辑需要具备组件/对象技术,例如,Microsoft® 分布式组件对象模型 (DCOM)、Object Management Group 的公共对象请求代理程序体系结构 (CORBA) 或 Sun 的远程方法调用 (RMI)。这些技术提供了可靠的、可升级的体系结构,以满足对应用程序日益增长的需要。

尽管这些基于组件的技术在 Intranet 环境中运行良好,但如果试图将其应用到 Internet 上,则会遇到两个大的问题。首先,这些技术不能进行交互操作。虽然这些技术都能处理对象,但在细节上却不尽相同。例如,生命周期管理、对构造函数的支持以及对继承的支持程度。第二,更重要的是,它们都着眼于 RPC 形式的通信,这通常会围绕对象方法的显式调用而构建紧密耦合的系统。

相反,基于浏览器的 Web 应用程序是松散耦合的,具有很强的互操作性。它们使用 HTTP 进行通信,交换许许多多不同格式的 MIME 类型数据。Web 服务使传统的 Web 编程模型适用于各种应用程序,而不仅仅是基于浏览器的应用程序。它们使用 HTTP 和其他 Internet 协议交换 SOAP 消息。由于 Web 服务依赖于 HTTP、XML、SOAP 和 WSDL 等行业标准,在 Internet 上展示应用程序的功能,因此它们独立于编程语言、平台和设备。

ASP.NET Web 服务基础结构通过将 SOAP 消息映射到方法调用,为 Web 服务提供了简单的 API。通过提供一种非常简单的编程模型(基于将 SOAP 消息交换映射到方法调用),它实现了此机制。ASP.NET Web 服务的客户端不需要了解用于创建它们的平台、对象模型或编程语言。而服务也不需要了解向它们发送消息的客户端。唯一的要求是:双方都要认可正在创建和使用的 SOAP 消息的格式,该格式是由使用 WSDL 和 XML 架构 (XSD) 表示的 Web 服务合约定义来定义的。

.NET Remoting 为分布式对象提供了一个基础结构。它使用既灵活又可扩展的管线向远程进程提供 .NET 的完全对象语义。ASP.NET Web 服务基于消息传递提供非常简单的编程模型,而 .NET Remoting 提供较为复杂的功能,包括支持通过值或引用传递对象、回调,以及多对象激活和生命周期管理策略等。要使用 .NET Remoting,客户端需要了解所有这些详细信息,简而言之,需要使用 .NET 建立客户端。(或者使用支持 .NET Remoting 的其他框架,我们所知道的唯一一个框架是 Intrinsyc 的用于 Java 的 Ja.NET。).NET Remoting 管线还支持 SOAP 消息,但必须注意这并没有改变其对客户端的要求。如果 Remoting 端点提供 .NET 专用的对象语义,不管是否通过 SOAP,客户端必须理解它们。

.NET Framework 支持两个截然不同的分布式编程模型 - Web 服务和分布式对象,这给开发人员造成了极大的混乱。系统何时应该使用 ASP.NET Web 服务,何时应该使用 .NET Remoting 呢?要回答这个问题,必须了解这两种技术的工作原理。

序列化和元数据

所有分布式通信管线最终都完成两项工作:将程序数据类型的实例封送到可以通过网络传送的消息中,并提供对那些消息的描述。前者是使用某种形式的序列化引擎(或称为封送拆收器)完成的,后者是通过某种形式的元数据完成的。例如,对于大多数(现代的)DCOM 接口来说,序列化引擎是类型库封送拆收器,而类型库提供元数据。ASP.NET Web 服务和 .NET Remoting 之间的主要不同在于它们如何将数据序列化为消息,以及它们为元数据选择的格式。

ASP.NET Web 服务、XmlSerializer 和 XSD

ASP.NET Web 服务依赖于 System.Xml.Serialization.XmlSerializer 类,在运行时将数据封送到 SOAP 消息中以及从 SOAP 消息中封送数据。对于元数据,它们生成 WSDL 和 XSD 定义,说明消息中包含什么样的内容。完全依赖于 WSDL 和 XSD 使 ASP.NET Web 服务元数据具有可移植性;它表示数据结构的方法,对于不同平台上使用不同编程模型的其他 Web 服务工具包也可以理解。在某些情况下,这限制了可以从 Web 服务中提供的类型 - XmlSerializer 只能封送可以用 XSD 表示的数据。也就是说,XmlSerializer 将不能封送对象图形,而且对于容器类型的支持也很有限。

尽管这些限制从传统的分布式对象的角度来看似乎很重要,但它们有助于确保与其他 Web 服务框架的互操作性 - 这是松散耦合的 Web 服务模型的基本目标。大量自定义属性使您能够注释数据类型,以控制 XmlSerializer 封送它们的方法,从而增强了对互操作性的支持。因此,您可以细致地控制在对象进行序列化时生成的 XML 的形状。另外,还可以对基于 ASP.NET 的 Web 服务进行调整,以便用文字 XSD 或 SOAP 编码规则(即 SOAP Section 5)描述消息。文字 XSD 是默认的,而且将成为以后的标准。它还包括 SOAP 编码支持,以便与现有的工具包进行互操作。这对用户很有帮助,特别是当用户需要与现有 Web 服务或客户端(它们需要使用预定义的消息格式进行通信)进行通信时更是如此。

.NET Remoting、IFormatter 和公共语言运行库

.NET Remoting 依赖于 System.Runtime.Serialization 引擎所使用的 IFormatter 接口的可插入实现程序向/从消息中封送数据。有两种标准的格式化程序:System.Runtime.Serialization.Formatters.Binary.BinaryFormatterSystem.Runtime.Serialization.Formatters.Soap.SoapFormatter。顾名思义,BinaryFormatterSoapFormatter 分别以二进制和 SOAP 格式封送类型。对于元数据,.NET Remoting 依赖于公共语言运行库程序集,该程序集包含它们实现的数据类型的所有相关信息,并通过反射提供它。对于元数据而言,依赖于程序集更容易保留全运行时类型的系统保真度。因此,当 .NET Remoting 管线封送数据时,它包括类中所有公共和专有的成员,正确处理对象图形并支持所有的容器类型(如 System.Collections.Hashtable)。但是,依赖运行时元数据也限制了 .NET Remoting 系统的使用范围 - 客户端必须理解 .NET 结构才能与 .NET Remoting 端点进行通信。除了可插入的格式化程序外,.NET Remoting 层还支持可插入的通道,该通道去除了有关消息发送方法的细节。有两种标准的通道,一种用于原始的 TCP,一种用于 HTTP。消息可以独立于格式,通过任意一种通道进行发送。

各有利弊:Remoting 和 Web 服务

SOAP 格式化程序和 HTTP 通道的存在产生了这样一个问题:可以使用 .NET Remoting 建立 Web 服务吗?回答是肯定的也是否定的。标准的 Web 服务技术堆栈不仅依赖于基于 SOAP 的消息,还依赖于消息基于 WSDL 和 XSD 的描述。Remoting 管线能够真正地生成描述端点所产生并使用的消息的 WSDL 定义。但是,如果沿着这条思路走下去,会产生几个问题。

首先,生成的 WSDL 文件总是用 SOAP 编码规则而不是文字 XSD 来描述消息。虽然现在这不是问题,但随着越来越多的工具完全着眼于架构,这种问题会越来越严重。

第二,生成的 WSDL 文件包括 .NET Remoting 专用的扩展功能。例如,下面是使用 .NET Remoting 提供其行为的一个简单的类。

public class Methods : MarshalByRefObject
{
// Now 方法返回当前的日期和时间
public string Now()
{
return System.DateTime.Now.ToString();
}
}


如果从此类中生成 WSDL,绑定信息将包括 .NET Remoting 专用的详细信息,如下所示。

<binding name='MethodsBinding' type='ns0:MethodsPortType'>
<soap:binding style='rpc'
transport='http://schemas.xmlsoap.org/soap/http'/>
<suds:class type='ns0:Methods' rootType='MarshalByRefObject'>
</suds:class>
<operation name='Now'>
<soap:operation soapAction=
'http://schemas.microsoft.com/clr/nsassem/RemSoap.Methods/methods#Now'/>
<suds:method attributes='public'/>
<input name='NowRequest'>...</input>
<output name='NowResponse'>...</output>
</operation>
</binding>


这些额外的元素是合法的,因为 WSDL 规范支持可扩展性。任何运作良好的 Web 服务工具包如果不理解它们都会简单地忽略它们。但是,有些是 Web 服务工具包不能忽略的。例如,下面是一个返回 Microsoft® ADO.NET System.Data.DataSet 的 Remoting 端点。

public class Methods : MarshalByRefObject
{
public System.Data.DataSet GetEmptyDataSet()
{
return new System.Data.DataSet();
}
}


下面是为此方法的输出消息生成的 WSDL 定义:

<message name='Methods.GetEmptyDataSetOutput'>
<part name='return' type='ns3:DataSet'/>
</message>


通常情况下,WSDL 消息指的是使用 XML 架构在特定的命名空间中定义的类型。但在这种情况下,用于 DataSet 类型的命名空间的前缀 ns3 不是在 XSD 中定义的,而是通过运行时隐式定义的。本例中的前缀 ns3 应绑定到由以下 URI 确定的 XML 命名空间

此 WSDL 定义的客户端是要了解这个“众所周知”的 URI 的特殊意义 - 它是 .NET Framework 中包括的特定运行时程序集的严格名称(由四部分组成)。这种 WSDL 对于使用 .NET Remoting 实现的客户端非常有用,因为它们可以使用适于封送的信息生成代理程序集。但是,对于不理解此 URI 并希望为 DataSet 类型找到架构定义的其他 Web 服务工具包(包括 ASP.NET),这种 WSDL 将毫无用处。

问题依然没有解决:可以使用 .NET Remoting 建立 Web 服务吗?严格地讲,可以。但是,不使用 .NET Remoting 管线的人能使用它们吗?回答是:也许可以,如果您小心地将端点减少到基本数据类型和语义。也就是说,如果您要与其他 Web 服务工具包进行互操作,则需要将参数限制到内置的简单类型和您自己的数据类型(不能使用 DataSet 这样的 .NET Framework 类型),而且要避免客户端激活的对象和事件。简而言之,如果您关心使用范围,则需要把自己限制到 ASP.NET Web 服务所支持的那些功能。

或者采取更好的方法,使用 ASP.NET Web 服务,因为这正是设计它们的目的所在。

分布式应用程序设计:ASP.NET Web 服务和 .NET Remoting

ASP.NET Web 服务支持 XML 架构类型系统,提供一种简单的编程模型,使用范围广,可以跨平台使用。.NET Remoting 支持运行时类型的系统,提供较复杂的编程模型,使用范围较窄。这种本质上的差别是决定使用哪种技术的主要因素。但是,还要考虑很多其他设计因素,包括传输协议、主机进程、安全性、性能、状态管理以及对事务的支持等。

传输协议和主机进程

尽管 SOAP 规范并不要求用 HTTP 作为传输协议,但是客户端只能通过 HTTP 访问使用 ASP.NET Web 服务实现的 Web 服务,因为它是 ASP.NET 支持的唯一一种传输协议。服务是通过 IIS 调用的,并在 ASP.NET 的辅助进程 aspnet_wp.exe 中执行。

.NET Remoting 使您能够在任何类型的应用程序(包括 Windows 窗体、托管的 Windows 服务、控制台应用程序或 ASP.NET 辅助进程)中灵活地托管远程对象。正如前面所述,.NET Remoting 提供两个传输通道 - TCP 和 HTTP。这两个通道都能使用套接字提供任意发送和接收进程之间的通信。

它还能将 HTTP 通道与 IIS 和 ASP.NET 辅助进程集成。这一点很重要,原因有以下几点。首先,它是当客户端请求到达时自动启动 .NET Remoting 端点的唯一方法。.NET Remoting 管线不包括启动远程服务器所需的 DCOM 类型的服务控制管理器 (SCM)。如果从任意进程中提供远程对象,则需要确保那些进程正在运行。还要确保它们是线程安全的,例如,在启动线程 B 以关闭进程后,线程 A 不能激活对象。如果从 ASP.NET 提供远程对象,则可以利用 Aspnet_wp.exe 辅助进程,这样既可自动启动又具有线程安全的优势。第二,与 IIS 集成是确保跨进程 .NET Remoting 调用的唯一途径,如下一节所述。

ASP.NET Web 服务和 .NET Remoting 基础结构都是可扩展的。您可以过滤入站和出站消息,从多方面控制类型封送和元数据的生成。使用 .NET Remoting,还能实现您自己的格式化程序和通道。

安全性

由于 ASP.NET Web 服务依赖于 HTTP,因此它们与标准的 Internet 安全性基础结构相集成。ASP.NET 利用 IIS 的安全性功能,为标准 HTTP 验证方案(包括基本、简要、数字证书,甚至 Microsoft® .NET Passport)提供了强有力的支持。(还可以使用 Windows 集成验证,但只能用于信任域中的客户端。)使用可用的 HTTP 验证方案的一个优势在于,无需在 Web 服务中更改代码,IIS 是在 ASP.NET Web 服务被调用之前执行验证的。ASP.NET 还支持基于 .NET Passport 的验证和其他自定义的验证方案。ASP.NET 支持基于目标 URL 的访问控制,并通过与 .NET 代码访问安全性 (CAS) 基础结构的集成支持访问控制。SSL 可用于确保通信的安全。

尽管这些标准传输技术对于确保 Web 服务相当有效,但它们只能做到这种程度。在涉及到不同信任域中多个 Web 服务的复杂情况下,还得建立自定义的特殊解决方案。Microsoft 和其他公司正致力于创建一套安全性规范,该规范将基于 SOAP 消息的可扩展性提供消息级别的安全性功能。这些规范之一是 XML Web 服务安全性语言(WS-Security [英文]),它为消息级别的凭据传输、消息完整性和消息保密定义了框架。

正如上一节所述,一般情况下,.NET Remoting 管线不能确保跨进程调用的安全。使用 ASP.NET 托管于 IIS 中的 .NET Remoting 端点可以利用 ASP.NET Web 服务可用的所有安全性功能,包括对使用 SSL 确保有线通信的安全性的支持。如果您正在使用托管在进程中的 TCP 通道或 HTTP 通道(而不是 aspnet_wp.exe),则必须自己执行身份验证、授权和保密机制。

另一个要关注的安全性问题是,在不必更改默认安全性策略的情况下,从不完全信任的环境中执行代码的能力。ASP.NET Web 服务客户端代理可以在这些环境中工作,但 .NET Remoting 代理则不能。要从不完全信任的环境中使用 .NET Remoting 代理,需要特殊的序列化权限。默认情况下,该权限不会授予从 Intranet 或 Internet 上下载的代码。如果要在不完全信任的环境中使用 .NET Remoting 客户端,则需要更改从那些区域中加载的代码的默认安全性策略。当您从运行于沙箱(如下载的 Windows 窗体应用程序)中的客户端连接到系统时,ASP.NET Web 服务是较简单的选择,因为不需要更改安全性策略。

状态管理

默认情况下,ASP.NET Web 服务模型采用无状态的服务体系结构;它并不是本能地与来自同一个用户的多个调用相关。另外,客户端每次调用 ASP.NET Web 服务时,都创建一个新的对象以服务于该请求。方法调用完成后,该对象即被破坏。要保持请求间的状态,可以使用 ASP.NET 页面所使用的技术(即会话和应用程序属性包),也可以执行自定义的解决方案。

.NET Remoting 支持许多状态管理选项,并且可能与来自同一个用户的多个调用相关或不相关,这取决于您选择的对象生命周期架构。SingleCall 对象是无状态的(如用于调用 ASP.NET Web 服务的对象),Singleton 对象共享所有客户端的状态,客户端激活的对象在每个客户端的基础上保持状态(带有其产生的所有相关的可升级性和可靠性问题)。

性能

从原始性能方面来讲,使用 TCP 通道和二进制格式化程序时,.NET Remoting 管线能够提供最快的通信。在我们进行的比较 ASP.NET Web 服务和 .NET Remoting 的相对性能的几乎所有的测试中,ASP.NET Web 服务在性能上都超出了使用 HTTP 或 TCP 通道的 SOAP 格式化程序的 .NET Remoting 端点。更有意思的是,使用二进制格式化程序和 HTTP 通道的 ASP.NET 和 .NET Remoting 端点在性能上非常相近。有关详细信息,请参阅性能比较:.NET Remoting 与 ASP.NET Web 服务。

企业服务

ASP.NET Web 服务或通过 .NET Remoting 提供的对象可以使用本地事务根据单个数据库协调工作。如果需要根据多个资源协调工作,它可以使用 .NET 企业服务(也称为 COM+)公布的事务(由 COM+ 管线管理的 DTC 分布式事务)。但要注意的是,ASP.NET Web 服务和 .NET Remoting 管线都不能传播公布的事务,因此两种端点都不可能通过跨进程的调用继承公布的事务。

这不一定是件坏事。一般来讲,公布的事务比本地事务代价要高,而要跨进程传播公布的事务,则代价会更高。如果您确实需要这种功能,简单的解决方案是在 .NET 企业服务服务器应用程序中展开一个从 System.EnterpriseServices.ServicedComponent 中衍生的类(有关详细信息,请参阅 COM+ Integration: How .NET Enterprise Services Can Help You Build Distributed Applications [英文])。对该类对象的跨进程调用将使用 DCOM 进行处理,以确保正确传播事务环境。较难的解决方案是使用底层的 API,手动传播分布的事务。

值得注意的是,传统的分布式事务模型一般不适用于松散耦合的 Web 服务。基于补偿事务的模型(即,撤消其他事务所提交工作的事务)更有意义,因为其隔离约束条件并不是很严格。在包括 Microsoft 的 Web 服务供应商中有一种普遍的说法,即 Web 服务空间需要的事务模型越灵活,该空间中进行的工作越多。等到定义出 Web 服务事务的标准方法时,您就可以根据情况使用本地或公布的事务实现自己的补偿架构了。

选择体系结构

如果您正在设计一个基于 .NET 的分布式应用程序,则需要考虑本文中讨论的所有问题,并对系统体系结构的应有结果得出一些结论。一般来讲,这比您想像的要容易些。但总有一些特殊的情况需要其他的方法,以下是您可以进行的某些一般假设,可为您简化情况。

首先,在默认情况下使用 ASP.NET Web 服务。它们的执行和使用都很简单,可以为客户端平台提供尽可能宽的使用范围,而且可以从默认安全性策略下沙箱中运行的代码中调用 ASP.NET Web 服务客户端代理代码。

如果要使用较传统的带有 CLR 类型保真度的分布式对象模型,不需要与其他平台进行互操作,而且由您控制客户端和服务器的配置,请考虑使用 .NET Remoting。如果您选择 .NET Remoting,最好使 HTTP 通道与 IIS 和 ASP.NET 集成,否则,必须建立自己的进程生命周期管理和安全性基础结构。假定 .NET Remoting 需要 .NET 客户端,使用二进制格式化程序而不是 SOAP 格式化程序是很有意义的;互操作性将不成问题,而且性能将显著提高。

最后,如果需要公布的事务,请使用企业服务 (COM+)。如果您执行 ServicedComponents,则出于性能方面的考虑,默认情况下它们将部署在库应用程序中。如果它们需要在远程计算机上运行,则将它们部署在服务器应用程序中。(如果您需要执行不同的进程安全性令牌 [而不是 aspnet_wp.exe 使用的令牌] 的代码,即使在相同的计算机上,可能也要考虑使用 COM+ 服务器应用程序。)

以下是三个基于这些理念的公共体系结构。

​​

图 1:简单的 3 层体系结构

图 1 显示了一个简单的 3 层体系结构,它带有 Web 服务器领域,支持一系列不同的客户端。所有服务器端的代码都在 ASP.NET 辅助进程 aspnet_wp.exe 中执行。这三种不同类型的客户端可以使用 HTTP 访问服务器领域。基于浏览器的客户端调用 ASP.NET Web 页面;多客户端(如 Windows 窗体应用程序、Microsoft® Visual Basic® 6 应用程序)和其他 Web 服务使用 ASP.NET Web 服务;根据需要,.NET 多客户端(如 Windows 窗体应用程序)和 Web 服务使用 ASP.NET Web 服务,或使用 HTTP 通道和二进制格式化程序的 .NET Remoting(假定它不在沙箱中)。

​​

图 2:使用 ASP.NET 的 n 层体系结构

一些非常大的应用程序使用一套辅助计算机从 Web 服务器的外层分担工作。这种体系结构如图 2 所示。请注意,在这种情况下,第二层也通过 ASP.NET 提供功能。

​​

图 3:使用企业服务 (COM+) 的 n 层体系结构

图 3 显示此体系结构的另一种版本,其第二层使用在 COM+ 中部署的 ServicedComponents 提供功能。

显然,这些并不是 .NET Framework 所支持的所有可能的体系结构。但是,它为您设计自己的系统提供了适当的基础。

小结

虽然 .NET Remoting 基础结构和 ASP.NET Web 服务都可以进行跨进程通信,但每种设计适用于不同的用户。ASP.NET Web 服务的编程模型很简单,使用范围很广。.NET Remoting 的编程模型较复杂,使用范围较窄。请务必了解这两种技术的工作原理,并选择适合您应用程序的技术。在任意一种情况下,都要使用 IIS 和 ASP.NET 管理进程生命周期,并提供一般的安全性。