.NET Remoting 体系结构评估

.NET Remoting 体系结构评估


Pat Martin
Microsoft Corporation
2003年5月

适用于:
    Microsoft® .NET Framework
    Microsoft® .NET Remoting

摘要:本文适用于要将 .NET Remoting 用于分布式多层应用程序设计的人员。文章从开发人员的角度介绍了该技术的功能。开发人员曾得益于这项技术所提供的方便的 RPC 机制,也曾感受过其不足之处带来的不便。本文假设读者熟悉 .NET Remoting,即使没有实际使用过,至少对其概念也有所了解。

产品特性一节对使用 Remoting 进行设计的人员很有用,最佳方法一节适用于使用 Remoting 进行构建的人员,Remoting 和 Web 服务一节试图消除有关“在何时选用何种技术”的困惑,摘要是对内容的精炼。

目录

概述

.NET Remoting 被誉为管理应用程序域之间的 RPC 的首选技术。应用程序域是公共语言运行库的隔离单元,它们是在进程内创建并运行的。这与 CLR 和非 CLR 托管的进程之间的进程间通信(互操作)不同。后一种类型的 RPC 通信(特别是 Web 上的)一般被认为是 Web 服务领域的问题。遗憾的是,这种看似清楚的区分,却由于可以在 IIS 下集成 .Net Remoting 服务器而变得模糊,正如 Microsoft .NET Remoting 框架简介一文中所述:

“通过在 IIS 中集成 .NET Remoting 对象,可以将其作为一种 Web 服务提供……”

一些 Microsoft 客户可能对 .NET Remoting 或多或少有些疑惑。我经常听到有人问“应该在什么时候使用 Remoting?”、“Remoting 何时会支持 NTLM?”、“如何保证远程会话的安全?”、“COM+ 怎么样?”以及“Remoting 如何管理事务?”

除了回答这些问题,本文还将介绍一些使用 .NET Remoting 的最佳方法,并概要介绍当前可以获得的功能。摘要预测了该技术的未来发展方向,特别是有关 Web 服务和新兴的全局 XML Web Service 体系结构 (GXA) 规范的问题。

产品特性一节的信息大部分来自 TechED N.Z. 2002,这次演示重点介绍了在分布式解决方案中使用 Remoting 的不同方法,阐明了 Remoting 的优点,也提到了一些不足之处。

最佳方法一节源于在多层 .NET 应用程序中使用 Remoting 的个人经验,其中介绍了很多在开发过程中用到的简单易行的最佳方法。

某些节包括了根据非正式谈话整理得到的资料,谈话的对象是对该技术及其发展方向都有深刻理解的 Microsoft 内部人员,但这里提供的信息决不代表未来的产品发布计划或安排。

产品特性

本节介绍 .NET Remoting 的功能和产品特性。

客户端/服务器通信

.NET Remoting 提供了一种很有用的方法,用于管理跨应用程序域的同步和异步 RPC 会话。远程对象代码可以运行在服务器上(如服务器激活的对象和客户端激活的对象),也可以运行在客户端上(其上的远程对象已经通过客户端/服务器的连接进行了序列化)。在任何一种情况下,只要完成初始化和配置(这并不困难),即可使用非常简单的编程语言,只需要少量的代码。远程对象(在按引用封送时是代理的对象)的使用对程序员是透明的。例如,早期的 Windows RPC 机制要求熟悉的类型和使用 IDL 工具的封送处理知识,并向开发人员公开 RPC 客户端和服务器存根的管理。Remoting 在为 .NET 提供 RPC 时要容易得多,而且由于使用简单易懂的 .NET 数据类型,从而消除了早期 RPC 机制中存在的类型不匹配的情况(这是一个非常大的威胁)。

默认情况下,可以将 Remoting 配置为使用 HTTP 或 TCP 协议,并使用 XML 编码的 SOAP 或本机二进制消息格式进行通信。开发人员可以构建自定义的协议(通道)或消息格式(格式化程序),并在需要时由 Remoting 框架使用。服务器和客户端组件都可以选择端口,就象可以选择通信协议一样。由此带来的一个好处是,很容易建立并运行基本的通信。

但是,在选择通信类型时还要考虑状态管理。本节接下来将介绍 Remoting 提供的各种通信选项及其相关的设计含义。

服务器激活的对象

“服务器激活的对象”是由服务器控制生存期的对象。它们只在客户端调用对象的第一个方法时,根据需要由服务器创建。服务器激活的对象只支持默认的构造函数。要对远程对象使用参数化的构造函数,可以使用“客户端激活”或“动态发布”(参见下文)。服务器激活的对象也被称为众所周知的对象类型,因为其位置 (URL) 是预先发布和已知的。服务器激活的对象有两种激活模式:SingletonSingleCall,下面将介绍这两种模式。要创建服务器激活类型的实例,可以通过编程的方法配置应用程序,也可以进行静态配置。服务器激活的配置相当简单,例如,以下代码片段

<service>
  <wellknown mode="SingleCall" type="Hello.HelloService, Hello" 
                   objectUri="HelloService.soap" />
</service>

描述了一个服务器激活的 (wellknown) 类型,其激活方式设置为 SingleCall。有关配置服务器激活的 Remoting 的详细信息,请参阅 MSDN 上 .Net Framework Developer's Guide 中的“Server-Side Registration”。

Singleton

这些对象遵循传统的 Singleton 设计模式,在这种模式中,任何时候内存中都只有一个实例,所有客户端都接受该实例提供的服务。但要注意,这些类型都有与之相关的默认生存期(请参阅下文的对象生存期管理一节)。这意味着对于可进行远程处理的类,客户端不必总是接收对这个类的同一实例的引用。后一种情况对状态管理很有意义,也是这种 Remoting 模式与传统的 Singleton 模式(要求对象标识相同)的不同之处。如果您的设计需要使用传统的 Singleton 状态管理模式,有两种方法可以解决此问题。一种方法是忽略默认的对象租用行为,以便“在主机应用程序域运行时始终”将对象保存在内存中。以下代码片段说明了如何做到这一点:

public class MyClass : MarshalByRefObject
{
  public override Object InitializeLifetimeService()
  {
      return null;
  }
}

如上所述,这种机制将对象锁定到内存中,防止对象被回收,但只能在主机应用程序运行期间做到这样。对于 IIS 集成,如果集成 Remoting 会话的 IIS 或 IIS 进程被回收(很多原因可以导致这种现象),那么对象将被破坏。

要完全依赖 Remoting 的线程安全的 Singleton 状态数据,我们需要做三件事:

  1. 忽略租用机制,使租用成为无限期的,如上所述。
  2. 将远程服务器集成在我们自己设计的进程中,例如,可以完全控制其生存期的系统服务。虽然此进程也可以被回收,但与回收 IIS 辅助进程相比,其操作更明显,更易察觉。有关此机制的详细信息,请参阅下文的产品特性一节。
  3. 将远程服务器开发为线程安全的服务器,因为这样可以使用多个线程来完成客户端的并发请求。这意味着,管理并发将写入共享资源并通常关注对静态内存的共享访问。

SingleCall

SingleCall 远程服务器类型总是为每个客户端请求设置一个实例。下一个方法调用将改由其他实例进行服务。从设计角度看,SingleCall 类型提供的功能非常简单。这种机制不提供状态管理,如果您需要状态管理,这将是一个不利之处;如果您不需要,这种机制将非常理想。也许您只关心负载平衡和可伸缩性而不关心状态,那么在这种情况下,这种模式将是您理想的选择,因为对于每个请求都只有一个实例。如果愿意,开发人员可以向 SingleCall 对象提供自己的状态管理,但这种状态数据不会驻留在对象中,因为每次调用新的方法时都将实例化一个新的对象标识。

动态发布

还需要考虑服务器激活方法的最后一个类型,即动态发布。这是一种服务器激活的类型,通过提供程序化的发布机制,可以对对象结构进行更多的控制。它允许在特定的 URL 发布特定的对象,并可以选择使用参数化的构造函数。从结构上讲,这种类型可以看作是服务器激活的 Singleton 类型的一个微小变形。有关动态发布的信息,请参阅 .NET Framework Developer's Guide

客户端激活的对象

“客户端激活的对象”是当客户端调用 newActivator.CreateInstance() 时在服务器上创建的。客户端本身使用生存期租用系统,可以参与到这些实例的生存期中。这种激活机制能够提供最广泛的设计灵活性。如果使用客户端激活,当客户端试图激活对象时,激活请求将发送到服务器。这种机制允许使用参数化的构造函数和针对每个客户端的连接状态管理。使用客户端激活,每个客户端接受其特定的服务器实例提供的服务,从而简化了多个调用时对象状态的保存过程。但使用这些对象时一定要谨慎,因为很容易忘记会话是分布式的,对象实际上不仅在进程之外,而且在多层应用程序的情况下,还有可能在计算机之外(在 Internet 上设置一个属性并不过分)。实用而不花哨的接口应该成为这里的准则:为了提高性能,我们可能需要在高度结合与松散耦合之间进行权衡。要创建客户端激活类型的实例,可以通过编程的方法配置应用程序,也可以进行静态配置。在服务器上进行客户端激活的配置相当简单,例如,以下代码片段

<service>
  <activated type="Hello.HelloService, Hello" 
             objectUri="HelloService.soap" />
</service>

描述了一个客户端激活的类型。请注意,我们不再需要 URL,因为对于客户端激活的类型,类型本身就足以激活了。另外,wellknown 标记已被 activated 标记替代。有关配置客户端激活的 Remoting 的详细信息,请参阅 MSDN 上 .Net Framework Developer's Guide 中的“Server-Side Registration”。

扩展性

在处理远程方法调用的过程中,.NET Remoting 将格式化的“消息”沿 Remoting 的“通道”从客户端发送到服务器。消息格式和通道本身都是完全可扩展和可自定义的。默认的通道或格式化程序都可以由自定义构建的组件所替代。消息在传输过程中可以在多个“接收点”被截取和更改,允许对消息进行自定义的处理(例如消息加密)。.NET Framework Developer's Guide (Sinks and Sink Chains) 中介绍了自定义机制,而且 Internet 上已经出现了一些自定义的通道和格式化程序(例如,Named Pipe 通道的实现)。大多数人对这种扩展性并不感兴趣,因为该技术提供的默认格式化程序和通道已经可以在最广的范围内使用(即 TCP 和 HTTP,尤其是与 SOAP 消息格式化程序一起使用)。但是在最初的设计阶段,需要考虑各种解决方案选项,记住这种功能还是有必要的。

异常传播

.NET Remoting 完全支持跨 Remoting 边界的异常传播,这是对使用错误代码,如 DCOM 的重大改进。

使用 Remoting 异常,最好将异常类标记为可序列化的并实现 ISerializable 接口。这样,可以跨 Remoting 边界对异常进行正确地序列化,也可以在构造过程中将自定义的数据添加到异常中。对于需要远程处理以及在使用中要保持一致的异常,最好定义您自己的异常类。确保使用此方法能捕获所有异常并进行正确传播,而且不允许未处理的异常跨过 Remoting 边界。

对象生存期管理

.NET Remoting 为管理远程对象的生存期提供了功能强大的机制。如果我们的服务器对象不保留任何状态(如 SingleCall 对象),那么不必关注此进程,只需让 Remoting 基础结构完成要完成的工作即可,需要时,对象将作为垃圾被回收。如果我们保留状态,无论是服务器激活的 Singleton 还是客户端激活的对象,我们可能都要参与生存期管理进程:对象租用。我们已经看到很小程度的参与,使用了一种简单(且有用)的方法,就是忽略 InitializeLifetimeService 方法,如以上对 Singleton 的介绍中所述。这就使我们能够在集成对象的进程运行期间始终保留对象。那么,这个对象生存期进程如何工作呢?

Remoting 提供的对象管理机制基于租用原则:您永远不会拥有一个对象,只是借用它,只要持续支付就可以一直使用它。此过程将在下文中进一步介绍。但是,首先要简单介绍一下在 COM 领域中是如何处理对象清理的。DCOM 综合使用 ping 和引用计数两种方法来确定对象是否仍在运行,这样做不仅容易出错,而且对网络带宽的要求很高。使用引用计数时,最坏的情况是从来不会被完全理解,最好的情况也是很脆弱。过去(现在仍是)要对引用计数应用一些简单的规则才能使其发挥作用。COM 对象的 IUnknown 接口包括了 AddRefRelease 方法,需要由开发人员在适当的时候调用。有时程序员弄错了,结果造成对象没被删除,还导致相关的内存泄露。

相反,Remoting 基于租用的生存期管理系统综合利用了租用、负责人和租用管理器。每个应用程序域都包含一个租用管理器,它将每个 Singleton 或客户端激活的对象的租用对象引用保存在其域中。每个租用可以有零个或多个相关的负责人,负责人能够在租用管理器确定租用过期时重新租用。这种租用功能是由 Remoting 基础结构通过 ILease 接口提供的,通过调用 InitializeLifetimeService 获得,如上文所述。ILease 接口定义了很多用于管理对象生存期的属性:

  • InitialLeaseTime。确定租用最初的有效期。
  • RenewOnCallTime。在每个方法调用后,更新此时间单元的租用。
  • SponsorshipTimeout。负责人通知租用过期后,Remoting 要等待的时间。
  • CurrentLeaseTime。距租用到期的时间(只读)。

租用过期后,租用管理器将通知所有租用负责人,询问他们是否要更新租用。如果不更新,将释放相关的对象引用。

负责人是可以为远程对象更新租用的对象。要成为负责人,您的类必须从 MarshalByRefObject 中导出并实现 ISponsor 接口。一个租用可以有多个负责人,一个负责人也可以参与多个租用。

有关使用这些接口进行编程的租用管理机制,请参阅 Lifetime Leases(英文)上的 .NET Framework Developer's Guide 文档,这里就不重复介绍了。但值得注意的是,这种功能强大的机制只是对管理有状态的远程对象的生存期有意义。如上所述,您或者完全忽略它,利用它在其进程容器运行时将对象保存在内存中,或者完全参与到租用机制中。

远程服务器集成

有很多方法可以集成 .NET 远程服务器,主要分为两大类,如下所述。

ASP.NET 下的 IIS 集成

在 IIS 下集成远程服务器端对象的能力是作为标准功能提供的。它有很多优势,包括支持安全性和可伸缩性。

要在 IIS 下集成对象:

  1. 开发远程类并从 MarshalByRefObject 中继承(或将类声明为可序列化)。
  2. 使用 IIS 管理器创建一个虚拟的 Web 应用程序。
  3. 将包含您的类的程序集放到虚拟 Web 应用程序的 bin 子文件夹中。
  4. 创建一个 web.config 文件以保存 Remoting 服务器的配置定义,并将它放置到 Web 应用程序的虚拟根目录中。

就这么简单。但是,您应该了解一些限制:

  • 不能为 IIS 集成指定应用程序名称,因为它是虚拟应用程序名称。
  • 必须使用 HHTP 通道。
  • 如果 Remoting 客户端也是一个 Web 应用程序,则启动时必须调用 RemotingConfiguration.Configure,它通常在 Global.asax 文件的 Application_Start 方法中。不能使用 <client> 标记来自动配置客户端 Web 应用程序。
  • 不要指定端口,因为 IIS 会进行端口分配。如果需要,您仍可以使用 IIS 管理来为虚拟应用程序指定端口。

Remoting 应用程序域将集成在 Aspnet_wp.exe 辅助进程中,默认情况下,它将采用该进程的标识。

注意:目前 ASP.NET 中有一个错误,要求将 Aspnet_wp.exe 辅助进程的进程标识设置为“system”或本地计算机帐户,默认设置中,machine.config 中的“machine”配置不正确,导致在域控制器的 IIS 下集成时,ASP.NET 应用程序出现错误 500“内部服务器错误”。可以论证的是,该错误是由于缺乏说明如何适当地配置计算机帐户的文档所造成的。

在 IIS 下集成有很多功能上的优势:默认情况下,可以提供伸缩性、线程、审核、身份验证、授权和安全通信等功能。ASP.NET 辅助进程一直在运行,并且可以使用 machine.config 中的 <processModel> 元素进行线程和错误管理方面的微调。简而言之,IIS 的优势和功能都可用于远程服务器。

但它也有一些缺点:您必须使用比 TCP 速度慢的 HTTP。另外,IIS 可能循环执行 ASP.NET 辅助进程,这将破坏所有 Singleton 的状态。对您来说,这可能是问题也可能不是问题,要取决于您的设计需要,因为客户端的下一个调用将重新启动 Singleton。您可以将 IIS 配置为不循环执行辅助进程,但这种能力很有限,特别是在 IIS 5 中,而且可能造成更进一步的影响。这里最根本的意思是,如果要求远程服务器的安全性,那么无疑要使用 IIS 集成。至于性能,只有在系统测试/使用过程中实际察觉到问题时,才需要考虑,而且总能在硬件上找到解决问题的办法。

IIS 下要考虑的身份验证问题

身份验证选项

.NET Remoting 没有自己的安全模式:身份验证和授权是由通道和主机进程执行的,在这种情况下则由 IIS 执行。Windows 身份验证可用于 Remoting,配置方法是在 web.config 中设置 <authentication mode="Windows"/>。不能使用表单或 Passport 身份验证,因为 Remoting 客户端不能访问 Cookie,也不能重新定向到登录页面(因为远程服务器是为非交互使用设计的)。

将凭据传递到远程对象

如果远程对象是 IIS 集成的(在 ASP.NET 辅助进程中)并配置为使用 Windows 身份验证,则必须使用通道的凭据属性指定要使用的凭据,否则将导致不传递任何凭据就进行远程调用。这种疏忽是 HTTP 访问拒绝响应的常见原因。要使用集成远程对象代理的进程(Remoting 客户端进程)的凭据,请将通道的凭据属性设置为由进程凭据缓存维护的 DefaultCredentials。这可以使用通道元素(用于 Web 客户端),即 <channel ref="http" useDefaultCredentials="true"/> 公开地完成,也可以使用以下代码通过编程方式完成:

IDictionary channelProperties;
channelProperties = ChannelServices.GetChannelSinkProperties(proxy);
channelProperties["credentials"] = CredentialCache.DefaultCredentials;

要随远程对象调用一起传递“特定的”凭据,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值