在运行于不同进程中的对象之间建立通讯(无论是在同一台计算机上,还是在相距数千公里的计算机上)是常见的开发目标,尤其是在生成大范围分布式应用程序的时候。传统上,这需要深入了解相关知识:不仅是关于通讯流任一端的对象的知识,而且还有关于低级别协议的主机、应用程序编程接口以及配置工具或文件的知识。简言之,它是一项需要大量专业知识和经验的复杂任务。
.NET 框架提供了几种可用来快速而方便地完成此任务的通讯方法,而无论您是否对协议和编码有大量的了解。因此,无论是需要快速开发 Web 应用程序,还是要花费更多时间生成关键的企业范围的应用程序(在此过程中涉及许多计算机或操作系统,并使用多种协议和序列化优化),.NET 框架都会支持您的方案。跨进程通讯仍然是一项复杂的任务,但现在它的许多工作都由 .NET 框架处理。
选择 .NET 中的通讯选项
.NET 框架提供了几种与不同应用程序域中的对象进行通讯的方式,每一种方式都具有特定级别的专业性和灵活性。例如,Internet 的发展已经使 XML Web services 成为一种颇具吸引力的通讯方法,这是因为 XML Web services 是建立在使用 XML 的 HTTP 协议和 SOAP 格式化的通用基础结构之上的。这些都是公共标准,并且可以直接与当前的 Web 基础结构结合使用,而无需担心其他的代理或防火墙问题。
然而,如果只是存在与在 HTTP 连接上使用 SOAP 序列化相关的性能问题,那么并不是所有的应用程序都应使用某种形式的 XML Web services 来建立。下面的子节应有助于您确定要为应用程序采用哪种形式的对象间通讯。
ASP.NET 还是远程处理?
ASP.NET 和 .NET 远程处理都是进程间的通讯实现方法。ASP.NET 提供一种由 Internet 信息服务 (IIS) 承载的基础结构,该结构擅长处理基本类型,而且是 Web 应用程序开发人员所熟悉的。.NET 远程处理是一般性的且具有高度可扩展性的进程间通讯系统,可以用来创建在 IIS 中承载的 XML Web services(并具有 ASP.NET 和 IIS 的所有安全性、可伸缩性以及会话和应用程序状态)或任何其他类型的通讯。
所需的通讯类型和您所熟悉的编程模型是您在两种编程模型之间进行选择的主要依据。请首先选择所需的进程间通讯类型,然后选择能以最容易的方式最佳地实现您的决定的编程模型。以下是选择您可能需要的进程间通讯类型的一些依据(按优先级排序):
1. 安全需要。如果需要保证调用的安全,则必须使用在 IIS 中承载的基于 HTTP 的应用程序,无论它是 ASP.NET 应用程序还是远程处理应用程序。在 IIS 中,基于 ASP.NET 和 .NET 远程处理的应用程序都具有您可能需要的所有安全功能。使用任何其他传输协议,或是在 IIS 外使用 HttpChannel,都需要您自己完成安全工作。请注意,在使用 HTTP 连接时无需使用 SOAP 编码格式;可以使用二进制编码来提高速度。
2. 速度。如果每个调用的效率很关键,则应当使用二进制编码,即使您不使用默认的 TcpChannel 也如此。在远程处理中,可以结合使用二进制格式化程序和 HttpChannel;在 ASP.NET 中,可以使用 POST 通讯。单是使用二进制格式化就可以显著地提高远程处理中的调用性能,即使您使用的是 HttpChannel 也如此。如果没有任何安全问题(例如,如果正在生成完全在防火墙内部运行的小应用程序),则使用二进制格式化的默认 TcpChannel 就能够达到最佳性能。
3. 交互操作。如果必须在不同的操作系统之间进行交互操作,则无论使用 .NET 远程处理还是使用 ASP.NET,都应当使用 SOAP 格式化协议。
4. 可缩放性。无论使用 .NET 远程处理还是使用 ASP.NET,将应用程序承载在 IIS 内部都会获得所需的可缩放性。
5. 随机变更。有几种问题可能会与您有关,例如:
· 编程模型易于使用。
· 易于实现扩展性。
· 自定义需要。
· 使用 C++ 的托管扩展。
· 对于远程对象激活、生存期或用户权限的特殊需要。
· 是否需要完全的类型保真。
.NET 远程处理系统为所有这些要求提供了解决方案,而 ASP.NET 系统只对其中某些要求提供支持。
以下是使用 ASP.NET、System.Net 命名空间和 .NET 远程处理生成的 XML Web services 之间存在的一些差异的简短摘要。
XML Web services
如果您是生成 ASP 应用程序并希望使用 Web 应用程序模型和 ASP.NET HTTP 运行库的功能(包括 Microsoft Visual Studio .NET 中的强大支持),则使用 ASP.NET 生成的 Web 服务可以满足您的要求。通过 XML Web services 基础结构,可以使用最适合于基于 Web 的应用程序的协议、格式和数据类型来方便地创建自己的组件供其他应用程序使用,或是使用他人创建的组件。它不支持 .NET 计算机之间的完全类型保真,并且仅可以传递某些类型的参数。
System.Net 命名空间
可以使用 System.Net 命名空间中的类从无到有生成整个通讯结构。还可以使用 System.Net 类实现您自己的可以插入到远程处理结构中的通讯协议和序列化格式。
.NET 远程处理
.NET 远程处理提供用于实现任意数量的全面通讯方案(包括但不仅限于 XML Web services)的工具。使用 .NET 远程处理可以:
在任意类型的应用程序域中发布或使用服务,无论该域是控制台应用程序、Windows 窗体、Internet 信息服务 (IIS)、XML Web services 还是 Windows 服务。
在二进制格式的通讯中保持完整的托管代码类型系统保真度。
注意 XML Web services 使用 SOAP 格式化,这种格式化不会保持所有的类型详细信息。
通过引用传递对象并返回到特定应用程序域中的特定对象。
直接控制激活特性和对象生存期。
实现和使用第三方信道或协议来扩展通讯以满足特定要求。
直接参与通讯进程以创建所需的功能。
.NET 远程处理概述
可以使用 .NET 远程处理来使不同的应用程序能够互相通讯,而无论这些应用程序是驻留在同一台计算机上、位于同一局域网中的不同计算机上还是位于相隔万里的差异巨大的网络中(即使计算机运行不同的操作系统)。
.NET 框架提供多种服务(如激活和生存期控制),以及负责在本地和远程应用程序之间传输消息的通讯信道。格式化程序用于在沿信道发送消息之前对消息进行编码和解码。应用程序可以在性能很关键时使用二进制编码,或是在与其他远程处理系统的互操作性至关重要时使用 XML 编码。远程处理的设计考虑到了安全性,因此您可以使用一些挂钩来访问调用消息和序列化流,以便在通过信道传输它们以前保证它们的安全。
远程处理基础结构是进程间通讯的抽象方法。系统的大部分在运行时无需关心。例如,可以通过值传递或可以复制的对象是自动在不同应用程序域中的或不同计算机上的应用程序之间传递的。只需将自定义类标记为可序列化便可使它工作。
然而,远程系统的真正优点在于它具有使位于不同应用程序域或者进程(它们使用不同的传输协议、序列化格式、对象生存期方案和对象创建模式)中的对象互相通讯的能力。此外,如果出于任何原因,需要干预通讯进程的几乎任何阶段,远程处理使这种干预变为可能。
无论您已经实现了一些分布式应用程序,还是只对将组件移动到其他计算机上以增加程序的可伸缩性感兴趣,您都可以非常容易地将远程处理系统理解为一般性的进程间通讯系统,它具有一些能够轻松处理大多数方案的默认实现。下面的讨论从使用远程处理进行进程间通讯的基础知识开始。
副本与引用
进程间通讯需要一个向其进程外的调用方提供功能的服务器对象、一个在服务器对象上进行调用的客户端以及一个将调用从一端运送到另一端的传输机制。服务器方法的地址是逻辑地址,并且在一个进程中正常工作,但不能在其他客户端进程中正常工作。若要解决此问题,客户端调用服务器对象的一种方法是:创建对象的完整副本并将该副本移动到客户端进程(在该进程中可以直接调用副本的方法)。
然而,许多对象无法或不应复制和移动到某个其他进程来执行。具有许多方法的非常大的对象不适合复制到或通过值传递到其他进程。通常,客户端仅需要由服务器对象上的一个或几个方法返回的信息。复制整个服务器对象(包括可能是与客户端需求无关的大量内部信息或可执行结构)将是对带宽以及客户端内存和处理时间的浪费。另外,许多对象公开公共功能,但是需要用于内部执行的私有数据。复制这些对象会使恶意客户端能够查看内部数据,从而产生安全问题隐患。最后,某些对象使用的数据干脆就无法以任何可理解的方式复制。例如,FileInfo 对象包含一个对操作系统文件的引用,此文件在服务器进程的内存中具有唯一的地址。可以复制这个地址,但它在另一进程中将永远不会具有任何意义。
在这些情况下,服务器进程应当向客户端进程传递一个对服务器对象的引用,而不是传递该对象的副本。客户端可以使用此引用来调用服务器对象。这些调用不在客户端进程中执行。相反,远程处理系统收集关于调用的所有信息并将其发送到服务器进程,在该进程中,将解释这些信息并查找正确的服务器对象,然后代表客户端对象向该服务器对象发出调用。然后,调用的结果被发送回客户端进程以返回到客户端。带宽仅用于关键信息:调用、调用参数以及任何返回值或异常。
简化的远程处理结构
使用对象引用在服务器对象和客户端之间进行通讯是远程处理的核心。然而,远程处理结构向程序员提供了更简单的过程。如果正确配置了客户端,只需要使用 new(或托管编程语言中的实例创建函数)创建远程对象的新实例。客户端接收对服务器对象的引用,然后就可以像该对象位于您的进程中而不是运行在另外一台计算机上一样调用其方法。远程处理系统使用代理对象来产生服务器对象位于客户端进程中的效果。代理是将它们自身显示为某个其他对象的临时代理对象。当客户端创建远程类型的实例时,远程处理基础结构创建对于客户端来说看起来与远程类型完全相同的代理对象。客户端调用此代理上的方法,而远程处理系统则接收调用,将其路由到服务器进程,调用服务器对象,并将返回值返回到客户端代理,而客户端代理将结果返回到客户端。
远程调用必须以某种方式在客户端和服务器进程之间传送。如果要自己生成远程处理系统,可以从学习网络编程、各种各样的协议和序列化格式规范开始。在 .NET 远程处理系统中,打开网络连接和使用特定协议将字节发送到接收应用程序所需的基础技术的组合被表示为传输信道。
信道是一个承载数据流,根据特定网络协议创建包并将该包发送到另一台计算机的类型。某些信道只能接收信息,另外一些只能发送信息,还有一些(例如默认的 TcpChannel 和 HttpChannel 类)可以在两个方向上使用。
虽然服务器进程了解有关每个唯一类型的一切信息,但是客户端仅知道它需要对其他应用程序域(可能在其他计算机上)中的某个对象的引用。从服务器应用程序域外的世界来说,该对象是通过 URL 查找的。向外部世界表示唯一类型的 URL 是激活 URL,它们确保远程调用是向正确的类型发出的。
完整的远程处理系统设计
假定您的应用程序在一台计算机上运行,而您想使用由存储在另一台计算机上的类型公开的功能。下图显示常规的远程处理过程。
远程处理过程
如果关系两端都是正确配置的,则客户端仅创建一个服务器类的新实例。远程处理系统创建一个表示该类的代理对象,并向客户端对象返回一个对该代理的引用。当客户端调用方法时,远程处理基础结构接应该调用,检查类型信息,并通过信道将该调用发送到服务器进程。侦听信道获得该请求并将其转发给服务器远程处理系统,服务器远程处理系统查找(或在必要时创建)并调用被请求的对象。然后,此过程将反向进行,服务器远程处理系统将响应捆绑成消息并由服务器信道发送到客户端信道。最后,客户端远程处理系统通过代理将调用的结果返回给客户端对象。
为使此过程工作只需要非常少的实际代码,但应当认真考虑关系的设计和配置。即使代码完全正确,仍然可能因为 URL 或端口号不正确而失败。
虽然远程处理进程的这种高级别概述相当简单,但低级别的详细信息可能是非常复杂的。其他主题中提供了对远程处理的主要元素的更为深入的讨论,如下面所列出的主题所示。
可远程处理的和不可远程处理的对象
请一定记住,在某个应用程序域中创建并因此特定于它的对象可以在此域中直接调用,但在此对象可以在它的域以外使用之前必须进行一些特殊处理。并非每种类型的对象都可以跨域的边界高效地发布或使用;因此,必须根据应用程序的需要决定要发布哪种类型的对象。
根据分布式应用程序的用途,有两种简单的对象类别:可远程处理的对象和不可远程处理的对象。不可远程处理的对象不向系统提供复制它们或在其他应用程序中表示它们的任何方法。因此,这些对象仅可以从它们的原始应用程序域中访问。可远程处理的对象既可以使用代理在其应用程序域或上下文外部访问,也可以复制它们并且可以将这些副本传递到它们的应用程序域或上下文外;换句话说,某些可远程处理的对象通过引用传递,而另一些通过值传递。
不可远程处理的对象
某些对象不能离开它们的应用程序域;它们永远不会被封送处理,原因是它们不声明序列化方法。这些不可远程处理的对象专用于创建它们的同一应用程序域内部,并且总是从该同一应用程序域中直接访问。.NET 框架类库中的大多数基类是不可远程处理的对象。
可远程处理的对象
可远程处理的对象是能在大范围的分布环境中正常运行的对象。有两种主要的可远程处理的对象:
按值封送对象,它们被复制并传出应用程序域。
按引用封送对象,将为其创建代理,而该代理由客户端用于远程访问对象。
按值封送对象
按值封送 (MBV) 对象声明它们的序列化规则(通过实现 ISerializable 来实现其自身的序列化,或者通过用 SerializableAttribute 修饰,该属性通知系统自动序列化该对象),但是不扩展 MarshalByRefObject。远程处理系统创建这些对象的完整副本并将副本传递到进行调用的应用程序域。一旦副本到达调用方的应用程序域内,对它的调用就是对该副本的直接调用。而且,当 MBV 对象作为参数传递时,也是通过值传递的。除声明 SerializableAttribute 或实现 ISerializable 之外,无需做其他任何事情就可以将类的实例跨应用程序或上下文边界通过值传递。
当由于性能或处理原因,将对象的完整状态和任何可执行功能移动到目标应用程序域有意义时,应当使用 MBV 对象。在许多方案中,这减少了跨网络、进程和应用程序域边界的冗长而耗费资源的往返行程。MBV 对象还可以从对象的原始应用程序域内直接使用。在这种情况下,由于不进行任何封送处理,因此不创建任何副本而且访问非常高效。
另一方面,如果发布的对象非常大,那么在繁忙的网络上传递整个副本对于应用程序来说可能不是最佳的选择。此外,永远不会将对复制对象的状态所做的更改传回到起始应用程序域中的原始对象。在抽象级别上,这种方案类似于客户端浏览器所请求的静态 HTML 页的方案。服务器复制文件,将其写入到流中,发送出去,然后忘掉它。所有后续的请求都只是对其他副本的其他请求。
远程处理系统广泛使用可序列化的对象。对其他应用程序域中的对象的引用(由 ObjRef 类在远程处理系统中表示)本身是可序列化的;必须能够将它精确地复制并将副本发送给请求。同样,对于实现 IMessage 的消息对象也是如此,这是因为它们是调用信息和所有其他所需对象引用的一般容器。另外,仅传输数据的对象通常是 MBV 对象。例如,DataSet 扩展实现 ISerializable 的 MarshalByValueComponent。
按引用封送的对象
按引用封送 (MBR) 的对象是至少扩展 System.MarshalByRefObject 的可远程处理的对象。根据已声明的激活类型,当客户端在自己的应用程序域中创建 MBR 对象的实例时,.NET 远程处理基础结构在调用方的应用程序域中创建表示该 MBR 对象的代理对象,并向调用方返回对此代理的引用。然后客户端将在该代理上进行调用;远程处理封送这些调用, 将它们发送回起始应用程序域,并在实际对象上调用该调用。
注意 如果客户端位于与 MBR 对象相同的应用程序域中,基础结构将向客户端返回对该 MBR 对象的直接引用,从而避免封送处理的系统开销。
如果 MarshalByRefObject 作为参数传入,当调用到达时,它变成另一个应用程序域中的代理。MBR 返回值,并且 out 参数以相同的方式工作。
当对象的状态和任何可执行的功能应处在创建它的应用程序域中时,应当使用 MBR 对象。例如,具有内部字段且该内部字段是操作系统句柄的对象应扩展 MarshalByRefObject,这是因为操作系统句柄在其他进程中或其他计算机上的其他应用程序域中是无意义的。有时对象可能大得难以想象;对于功能强大的服务器还行,但通过网络发送到 33.6 kbps 的调制解调器就不行了。
上下文绑定对象
上下文绑定对象是从 System.ContextBoundObject(它本身从 System.MarshalByRefObject 继承)继承的 MBR 对象。可以将上下文当作应用程序域的子部分,它在执行期间为驻留在其中的对象提供某种资源充足的环境(例如保证对象不会被多个线程同时访问)。每个应用程序域都具有默认的上下文,大多数托管代码使用应用程序域的默认上下文创建对象并直接从同一应用程序域内部调用成员,而不会产生与上下文有关的问题。所有从 ContextBoundObject 继承的类型都作为代理向其他上下文公开,无论它们是否在同一应用程序域中。
例如,假定有一个某类型上的方法,该类型是事务的一部分因而受到特定于在其中创建它的上下文的规则的约束。应当使该类型从 ContextBoundObject 继承,这样就可以从对象本身的上下文访问该对象,而且系统可以强制实施有关与对象及其方法关联的事务的规则。如果从同一应用程序域内部的另一上下文中调用 ContextBoundObject,则将为调用方创建代理,但是上下文间的通讯不通过信道系统,从而在此情况下提高了调用效率。
考虑要远程处理哪种类别的类型时,请决定需要通过哪些边界。特定于某个特定上下文的对象只能从该上下文中直接访问。对于特定于某个特定应用程序域的对象也是如此。若要远程处理这两者中的任一种对象,在从服务器对象所特定于的边界内调用该服务器对象之前,远程处理系统必须成功通过上下文边界、应用程序边界或是成功通过这两种边界。由于通过每一边界都要花费处理时间,因而为了决定服务器应当是何种类型的可远程处理对象,就需要确定对象必须通过哪些边界。如果无需上下文检查就调用对象,则不应让远程类型扩展 ContextBoundObject(MarshalByRefObject 将执行得更好)。如果确实需要上下文检查,则应当扩展 ContextBoundObject,但是需要明白:在对象上进行调用之前,必须通过附加的边界。
对象激活和生存期
多数情况下,无需注意创建对象的确切时间;只需在对象上调用方法时让它作出响应。然而,当生成远程对象时,必须知道新对象是何时以及如何创建和初始化的,即它是如何激活的。在可以使对象对于客户端可用之前,远程处理系统必须总是知道需要哪种类型的激活;因此,理解您的选择是很重要的。
激活
对于按引用封送 (MBR) 的对象,有两种激活类型:服务器激活和客户端激活。
服务器激活的对象:
只在需要它们时由服务器创建——不是在通过调用 new 或 Activator.GetObject() 创建客户端代理时创建,而是在客户端调用该代理上的第一个方法时创建。
可以声明为 Singleton 或 SingleCall 对象。Singleton 对象是这样的对象:无论该对象有多少个客户端,总是只有一个实例,且该对象具有默认的生存期。将对象声明为 SingleCall 对象时,系统为每个客户端方法调用创建一个新对象。客户端可以使用生存期租约系统参与这些实例的生存期。
客户端激活的对象:
客户端调用 new 或 Activator.CreateInstance() 时在服务器上创建。使用生存期租约系统,客户端本身可以参与这些实例的生存期。
生存期租约
按引用封送的对象 (MBR)无论是服务器激活的 Singleton 对象还是客户端激活的对象,都不会永远驻留在内存中。相反,除非该类型重写 MarshalByRefObject.InitializeLifetimeService() 以控制自己的生存期策略,否则每个 MBR 的生存期都是由租约、租约管理器和一些主办方的组合控制的。(在这种情况下,MBR 对象的生存期是对象在内存中保持活动状态的总时间。)租约本身是 .NET 远程处理系统开始删除某个特定对象并回收内存的进程之前,该特定对象在内存中保持活动状态的时间。服务器应用程序域的租约管理器是确定何时标记远程对象以供进行垃圾回收的对象。主办方是可以通过将其本身注册到租约管理器来为特定对象请求新租约的对象。
只要 MBR 对象被在应用程序域外部进行远程处理,就为该对象创建生存期租约。每个应用程序域都包含一个负责管理其域中的租约的租约管理器。租约管理器定期检查所有租约以确定过期的租约时间。如果租约已过期,租约管理器将为该对象遍历它的主办方列表并请求它们中是否有谁要续订租约。如果没有任何主办方续订该租约,租约管理器将移除该租约,该对象被删除,其内存被垃圾回收机制回收。因此,如果对象被主办方多次续订租约或被客户端持续调用,其生存期可以比其生存期租约长得多。
由于远程对象的生存独立于其客户端的生存,因此简单或轻量对象的租约(举个例子来说)可以很长并被一些客户端使用,前提是该租约被管理器或客户端定期续订。这种方法高效地使用租约,原因是分布式垃圾回收所需的网络通信量很小。另一方面,对于使用时间不太长和使用稀有资源的远程对象来说,其租约的生存期可以很短,以至于客户端用较短的时间频繁地续订其生存期。当所有客户端都完成对远程对象的处理时,.NET 远程处理系统将很快删除该对象。这种策略增加了网络通信量以更有效地使用服务器资源。
使用租约管理远程对象的生存期是引用计数的一种替换方法,引用计数在不可靠的网络连接上可能是复杂而低效的。虽然可以将租约配置为延长远程对象的生存期以超过所需的精确长度,但是由于减少了专用于引用计数和进行 ping 操作的客户端的网络通信量,因而使租约在为特定方案正确配置时成为一种很有吸引力的解决方案。
租约有五个主要属性:
InitialLeaseTime。它指定对象在租约管理器开始删除对象的过程之前将保留在内存中的初始时间长度。在配置文件中,它是 <lifetime> 配置元素的 leaseTime 属性。默认为 5 分钟。租约时间为零则将租约设置为具有无限长的生存期。
CurrentLeaseTime。它指定在租约过期之前所剩下的时间长度。当租约被续订时,其 CurrentLeaseTime 被设置为 CurrentLeaseTime 的最大值或 RenewOnCallTime。
RenewOnCallTime。它指定对对象进行各个远程调用后将 CurrentLeaseTime 设置为的最大时间长度。默认为 2 分钟。
SponsorshipTimeout。它指定租约管理器被通知租约已过期时租约管理器等待主办方响应的时间。如果主办方未在指定的时间内响应,则移除该主办方并调用另一主办方。如果没有其他主办方,则租约过期,且垃圾回收器将处置该远程对象。如果值为“0”(TimeSpan.Zero),则租约将不注册主办方。默认为 2 分钟。
LeaseManagerPollTime。它是租约管理器在检查过期的租约之后休眠的时间量。默认的 LeaseManagerPollTime 为 10 秒钟。
当 MBR 对象在其他应用程序域中被激活时创建租约。此时,当 ILease.CurrentState 属性为 LeaseState.Initial 时,可以设置租约的属性。一旦设置,就不能直接更改它们。只可以从 ILease.Renew 调用中或者当租约管理器在主办方上调用 ISponsor.Renewal 且该主办方用一个 TimeSpan 响应时更改 CurrentLeaseTime。MarshalByRefObject 具有生存期租约的默认实现,且除非该租约在创建时被修改,否则租约属性将始终相同。
修改租约属性
可以通过两种方式修改生存期租约属性。可以声明自定义生存期租约属性,方法是重写 MBR 对象中的 MarshalByRefObject.InitializeLifetimeService 以自己设置租约的属性,或者将其重写为返回 null(Visual Basic 中为 Nothing);后一种方法通知 .NET 远程处理系统该类型的实例将具有无限长的生存期。您或者管理员还可以在特定的应用程序或计算机配置文件中的 <lifetime> 元素内指定该应用程序中所有对象的生存期属性。
创建后,可以用三种方式续订租约:
客户端直接调用 ILease.Renew 方法。
如果设置了 ILease.RenewOnCallTime 属性,则每个对远程对象的调用都将为租约续订指定的时间。
租约调用 ISponsor.Renewal 方法以请求续订租约,而主办方则以一个 TimeSpan 响应。
租约管理器
租约管理器的任务之一是定期检查租约的时间是否过期。当租约的时间已过期时,该租约将接到通知并尝试通过调用其主办方来续订自己。
租约管理器还维护一个租约正等待其答复的主办方的列表。如果主办方未在 SponsorshipTimeOut 时间长度所指定的间隔内响应,则将其从主办方列表中移除。
租约被允许过期时,它不再接受任何其他租约消息或主办方返回的内容。租约的引用被从租约列表中移除,而且 .NET 远程处理系统将把该对象引用从其内部表中移除。然后,垃圾回收系统将移除该租约和对象。
信道
信道是跨远程处理边界(无论是在应用程序域、进程还是计算机之间)在应用程序之间传输消息的对象。信道可以在终结点上侦听入站消息,向另一个终结点发送出站消息,或者两者都可以。这使您能够插入各种各样的协议,即使信道的另一端上没有公共语言运行库。
信道必须实现 IChannel 接口,该接口提供诸如 ChannelName 和 ChannelPriority 这样的信息性属性。被设计为在特定端口上侦听特定协议的信道实现 IChannelReceiver,而被设计为发送信息的信道实现 IChannelSender。TcpChannel 和 HttpChannel 对于这两种接口都加以实现,因此它们可用于发送或接收信息。
您可以以两种方式将信道注册到远程处理基础结构:
如果您正在发布可远程处理的对象,则在注册服务器对象之前调用 ChannelServices.RegisterChannel()。
如果您正在使用可远程处理的对象的功能,则在创建服务器对象的实例之前调用 ChannelServices.RegisterChannel()。
信道还可以从远程处理配置文件加载。
在客户端,消息经过客户端上下文链后,被传递到客户端信道接收链。第一个信道接收器通常是格式化程序接收器;它将消息序列化为流(然后该流将被沿着信道接收链传递到客户端传输接收器)。然后客户端传输接收器将此流写出到网络。
在服务器端,服务器传输接收器从网络读取请求,并将该请求流传递到服务器信道接收链。此链末端的服务器格式化程序接收器将该请求反序列化为消息。然后将此消息传递到远程处理基础结构。
信道选择
当客户端在远程对象上调用方法时,参数以及和调用有关的其他详细信息将被通过信道传输到远程对象。调用的任何结果均以相同的方式返回。客户端可以选择在服务器上注册的任何信道与远程对象通讯,这样就使开发人员能够自由地选择最符合他们的需要的信道。也可以自定义任何现有信道或者生成使用不同通讯协议的新信道。信道选择需要遵循以下规则:
在可以调用远程对象之前,服务器上的远程处理系统必须注册了至少一个信道。必须在注册对象之前注册信道。如果客户端上没有注册信道,则远程处理系统将选择或创建一个以发送出站调用。
注意 如果客户端需要回调函数,则必须在客户端上注册一个侦听信道,并且必须将服务器配置为使用一个兼容的信道。
信道是针对每个应用程序域注册的。单个进程可以包含多个应用程序域。当进程结束时,它注册的所有信道都自动销毁。
信道名称在应用程序域中必须是唯一的。例如,由于默认信道具有名称,因此,若要在一个应用程序域中注册两个 HttpChannel 对象,就必须在注册它们之前更改信道的名称。下面的 C# 代码示例对此进行了演示。 · IDictionary prop = new Hashtable();
· prop["name"] = "http1";
· prop["port"] = "9001";
ChannelServices.RegisterChannel(new HttpChannel(prop, null, null));
不能多次注册在特定端口上侦听的信道。虽然信道是针对每个应用程序域注册的,但是同一台计算机上的不同应用程序域不能注册在同一个端口上侦听的同一个信道。
如果您不能确定是否有可用的端口,那么在配置信道的端口时请使用“0”(零),远程处理系统将为您选择一个可用的端口。
客户端可以使用任何已注册的信道与远程对象进行通讯。当客户端尝试连接到远程对象时,远程处理系统将确保该远程对象被连接到正确的信道。客户端负责在尝试与远程对象通讯之前调用 ChannelServices.RegisterChannel(),如果客户端需要回调函数,那么它必须注册一个信道和一个端口。
当客户端在代理上调用方法时,调用将被截获并捆绑为消息,然后被传递到 RealProxy 类的实例。RealProxy 类将消息转发到消息接收器以进行处理。消息接收器与由远程对象注册的信道建立连接,并通过该信道将消息调度到起始应用程序域中。在那里,该消息将被取消封送,并且将对远程对象本身进行调用。
当远程处理在客户端的域中初始化远程对象的代理时,将通过调用选定信道上的 IChannelSender.CreateMessageSink 从客户端配置的信道中检索一个能够与该远程对象通讯的消息接收器。
远程处理系统的一个易引起混淆的方面是远程对象和信道之间的关系。例如,如果对象仅在调用到达时才激活,SingleCall 远程对象将如何侦听要连接的客户端呢?
这是可能的,部分原因在于远程对象共享信道;远程对象不拥有信道。承载远程对象的服务器应用程序必须注册它们所需要的信道以及它们要使用远程处理系统公开的对象。信道注册之后,将自动在指定的端口开始侦听客户端请求。对于同步调用的情况,将在消息调用期间维护来自客户端的连接。由于每个客户端连接都是在它自己的线程中处理的,因此单个信道可以同时服务于多个客户端。
远程处理示例:异步远程处理
下面的示例应用程序说明远程处理方案中的异步编程。该示例首先创建远程对象的同步委托并且调用它来阐释等待该返回的线程。然后,它使用异步委托和 ManualResetEvent 来调用远程对象方法并等待响应。
该应用程序可在单台计算机上运行或通过网络运行。如果要在网络上运行该应用程序,必须用远程计算机的名称替换客户端配置中的“localhost”。
若要生成此示例,请保存下面的文件并在命令提示处键入下面的内容:
csc /t:library /out:ServiceClass.dll ServiceClass.cs
csc /r:ServiceClass.dll Server.cs
csc /r:ServiceClass.dll RemoteAsync.cs
然后,打开两个指向同一目录的命令提示符。在其中一个键入
Server
在另一个键入:
RemoteAsync
RemoteAsync.csusing System;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Channels;
using System.Threading;
public class RemotingDelegates : MarshalByRefObject{
public static ManualResetEvent e;
public delegate string RemoteSyncDelegate();
public delegate string RemoteAsyncDelegate();
// This is the call that the AsyncCallBack delegate will reference
[OneWayAttribute]
public void OurRemoteAsyncCallBack(IAsyncResult ar){
RemoteAsyncDelegate del = (RemoteAsyncDelegate)((AsyncResult)ar).AsyncDelegate;
Console.WriteLine("\r\n**SUCCESS**: Result of the remote AsyncCallBack: " + del.EndInvoke(ar) );
// Signal the thread.
e.Set();
return;
}
public static void Main(string[] Args){
// IMPORTANT: Remember that .NET Remoting does not remote
// static members. This class must be an instance before
// the callback from the async invocation can reach this client
RemotingDelegates HandlerInstance = new RemotingDelegates();
HandlerInstance.Run();
}
public void Run(){
// enable this and the e.WaitOne call at the bottom if you
// are going to make more than one async call.
e = new ManualResetEvent(false);
Console.WriteLine("Remote synchronous and asynchronous delegates.");
Console.WriteLine(new String('-',80));
Console.WriteLine();
// this is the only thing you must do in a remoting scenario
// for either synchronous or asynchronous programming -- configuration.
RemotingConfiguration.Configure("SyncAsync.exe.config");
// otherwise, from here on, the steps are identical to single-AppDomain
// programming.
ServiceClass obj = new ServiceClass();
// This delegate is a remote synchronous delegate
RemoteSyncDelegate Remotesyncdel = new RemoteSyncDelegate(obj.VoidCall);
// When invoked, program execution waits until method returns.
// This delegate could be passed to another application domain
// to be used as a callback to the obj.VoidCall method.
Console.WriteLine(Remotesyncdel());
// This delegate is an asynchronous delegate. Two delegates must be created.
// The first is the system-defined AsyncCallback delegate, which references
// the method the remote type calls back when the remote method is done
AsyncCallback RemoteCallback = new AsyncCallback(this.OurRemoteAsyncCallBack);
// Now create the delegate to the remote method you want to use asynchronously
RemoteAsyncDelegate RemoteDel = new RemoteAsyncDelegate(obj.TimeConsumingRemoteCall);
// Now we start the method call. Note that execution on this thread continues
// immediately without waiting for the return of the method call.
IAsyncResult RemAr = RemoteDel.BeginInvoke(RemoteCallback, null);
// If at some point you want to stop execution on this thread to wait for
// the return from this specific call, retrieve the IAsyncResult returned from
// the BeginIvoke call, obtain its WaitHandle, and pause the thread until,
// such as the next line:
// RemAr.AsyncWaitHandle.WaitOne();
// To wait in general, if, for example, many asynchronous calls have been
// made and you want notification of any of them, or, like this example,
// because the application domain can be recycled before the callback
// can print the result to the console.
//e.WaitOne();
// This simulates some other work going on in this Thread while the async
// call has not returned. If you're not doing work on this thread,
int count = 0;
while(!RemAr.IsCompleted){
Console.Write("\rNot completed: " + (++count).ToString());
// make sure callback thread can invoke callback
Thread.Sleep(1);
}
}
}
Server.csusing System;
using System.Runtime.Remoting;
public class Server{
public static void Main(){
RemotingConfiguration.Configure("server.exe.config");
Console.WriteLine("Waiting...");
Console.ReadLine();
}
}
ServiceClass.csusing System;
using System.Runtime.Remoting;
public class ServiceClass : MarshalByRefObject{
public ServiceClass() {
Console.WriteLine("ServiceClass created.");
}
public string VoidCall(){
Console.WriteLine("VoidCall called.");
return "You are calling the void call on the ServiceClass.";
}
public int GetServiceCode(){
return this.GetHashCode();
}
public string TimeConsumingRemoteCall(){
Console.WriteLine("TimeConsumingRemoteCall called.");
for(int i = 0; i < 20000; i++){
Console.Write("Counting: " + i.ToString());
Console.Write("\r");
}
return "This is a time-consuming call.";
}
}
Server.exe.config<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown
type="ServiceClass, ServiceClass"
mode="Singleton"
objectUri="ServiceClass.rem"
/>
</service>
<channels>
<channel
ref="http"
port="8080"
/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
SyncAsync.exe.config<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown
type="ServiceClass, ServiceClass"
url="http://localhost:8080/ServiceClass.rem"
/>
</client>
<channels>
<channel
ref="http"
port="0"
/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
总结
上面是VS.NET中.NET远程通信的一些概念和示例,给大家参考一下。有任何建议请MAIL我 paulni@citiz.net。