概述 .NET Remoting 被誉为管理应用程序域之间的 RPC 的首选技术。应用程序域是公共语言运行库的隔离单元,它们是在进程内创建并运行的。这与 CLR 和非 CLR 托管的进程之间的进程间通信(互操作)不同。后一种类型的 RPC 通信(特别是 Web 上的)一般被认为是 Web 服务领域的问题。 一些 Microsoft 客户可能对 .NET Remoting 或多或少有些疑惑。我经常听到有人问“应该在什么时候使用 Remoting?”、“Remoting 何时会支持 NTLM?”、“如何保证远程会话的安全?”、“COM+ 怎么样?”以及“Remoting 如何管理事务?”
除了回答这些问题,本文将介绍一些使用 .NET Remoting 的最佳方法,并概要介绍当前可以获得的功能。摘要预测了该技术的未来发展方向,特别是有关 Web 服务和新兴的全局 XML Web Service 体系结构 (GXA) 规范的问题。
其中介绍了很多在开发过程中用到的简单易行的最佳方法,和我在开发多层 .NET 应用程序中获得的Remoting 的个人经验。
客户端/服务器通信 .NET Remoting 提供了一种很有用的方法,用于管理跨应用程序域的同步和异步 RPC 会话。远程对象代码可以运行在服务器上(如服务器激活的对象和客户端激活的对象),也可以运行在客户端上(其上的远程对象已经通过客户端/服务器的连接进行了序列化)。在任何一种情况下,只要完成初始化和配置(这并不困难),即可使用非常简单的编程语言,只需要少量的代码。远程对象(在按引用封送时是代理的对象)的使用对程序员是透明的。例如,早期的 Windows RPC 机制要求熟悉的类型和使用 IDL 工具的封装处理知识,并向开发人员公开 RPC 客户端和服务器存根的管理。Remoting 在为 .NET 提供 RPC 时要容易得多,而且由于使用简单易懂的 .NET 数据类型,从而消除了早期 RPC 机制中存在的类型不匹配的情况(这是一个非常大的威胁)。
默认情况下,可以将 Remoting 配置为使用HTTP或TCP 协议,并使用 XML 编码的 SOAP 或本机二进制消息格式进行通信。开发人员可以构建自定义的协议(通道)或消息格式(格式化程序),并在需要时由 Remoting 框架使用。服务器和客户端组件都可以选择端口,就象可以选择通信协议一样。由此带来的一个好处是,很容易建立并运行基本的通信。
但是,在选择通信类型时还要考虑状态管理。接下来将介绍 Remoting 提供的各种通信选项及其相关的设计含义。
服务器激活的对象 “服务器激活的对象”是由服务器控制生存期的对象。它们只在客户端调用对象的第一个方法时,根据需要由服务器创建。服务器激活的对象只支持默认的构造函数。要对远程对象使用参数化的构造函数,可以使用“客户端激活”或“动态发布”(参见下文)。服务器激活的对象也被称为众所周知的对象类型,因为其位置 (URL) 是预先发布和已知的。服务器激活的对象有两种激活模式:Singleton 和 SingleCall,下面将介绍这两种模式。要创建服务器激活类型的实例,可以通过编程的方法配置应用程序,也可以进行静态配置。服务器激活的配置相当简单,例如,以下代码片段
<service><wellknown mode="SingleCall" type="Hello.HelloService, Hello" objectUri="HelloService.soap" /></service>
描述了一个服务器激活的 (wellknown) 类型,其激活方式设置为 SingleCall。有关配置服务器激活的 Remoting 的详细信息可参阅微软MSDN。
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。
客户端激活的对象 “客户端激活的对象”是当客户端调用 new 或 Activator.CreateInstance() 时在服务器上创建的。客户端本身使用生存期租用系统,可以参与到这些实例的生存期中。这种激活机制能够提供最广泛的设计灵活性。如果使用客户端激活,当客户端试图激活对象时,激活请求将发送到服务器。这种机制允许使用参数化的构造函数和针对每个客户端的连接状态管理。使用客户端激活,每个客户端接受其特定的服务器实例提供的服务,从而简化了多个调用时对象状态的保存过程。但使用这些对象时一定要谨慎,因为很容易忘记会话是分布式的,对象实际上不仅在进程之外,而且在多层应用程序的情况下,还有可能在计算机之外(在 Internet 上设置一个属性并不过分)。实用而不花哨的接口应该成为这里的准则:为了提高性能,我们可能需要在高度结合与松散耦合之间进行权衡。要创建客户端激活类型的实例,可以通过编程的方法配置应用程序,也可以进行静态配置。在服务器上进行客户端激活的配置相当简单,例如,以下代码片段
<service><activated type="Hello.HelloService, Hello" objectUri="HelloService.soap" /></service>
描述了一个客户端激活的类型。请注意,我们不再需要 URL,因为对于客户端激活的类型,类型本身就足以激活了。另外,wellknown 标记已被 activated 标记替代。
扩展性 在处理远程方法调用的过程中,.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 和引用计数两种方法来确定对象是否仍在运行,这样做不仅容易出错,而且对网络带宽






