随着本章节的开始,您将拥有您的第一个Remoting程序!当然,在开门见山地探讨代码之前,我会先给大家介绍一下的内容:.NET Remoting和其他品种的分布式应用程序框架之间的区别;远程对象的基本类型,即服务器端活动对象(server-actived objects)和客户端活动对象(client-activated objects);按值类型封送数据;关于生存期管理和原数据生成的一些基本信息,这些基本信息是客户端获悉服务器端对象接口所必需的。
.NET Remoting的优势
就像您在前面一章看到的那样,其它一些用于开发分布式应用程序的体系结构早已存在。您也许就此疑惑为什么.NET还要再费周折去引入另一套完全不同的方法呢?一个主要的原因也即优势之一就在于.NET Remoting以一些众所周知、定义明确的标准(例如HTTP和SOAP)为中心构建的。这使得它:
易于实现
把.NET Remoting与其它remoting架构相对比,就如同把COM组件的开发Visual Basic中进行与C++中实现作比较相类似。Visual Basic 6 允许开发人员把精力集中到应用程序自己的业务逻辑的实现上,而无必操心COM的技术细节。而C++的程序员们则必须了解精确的COM规范,并编写一箩筐的实现代码去支持应用程序的业务逻辑。
通过.NET,这种完全轻松的实现(Implementation)已被扩展到分布式应用程序的开发上来,我们再也不需要象Java RMI里那样去定义代理/存根-编辑循环(proxy/stub-compilation cycles),而且,我们使用象CORBA或者DCOM那样高深难懂的语言去定义接口的日子也一去不复返。.NET Remoting一个独特的特性在于:您不再需要事先确定远程请求的编码格式,取而代之的是,您可以在一个配置文件中通过更改一个英文单词就能在二进制格式和SOAP格式之间进行切换。您甚至可以通过在配置文件中新添一行来为同一对象提供两种不同的通信信道。同时,您不会像使用DCOM、COM+和Java EJB那样被限制在固定的一个平台或者编程语言上,.NET Remoting的配置和开发都比DCOM要轻松的多。
即使.NET Remoting提供了众多的即成特性,但它并没有限制我们的手脚。恰恰相反:.NET Remoting可以如您所愿的简单,也可以尽您所需的复杂。赋予一个对象远程特性所需的步骤可以是写两行代码那样直截了当,也可以精细复杂到您自己去实现一个给定的传输协议或者格式化程序。
可扩展性
.NET Remoting在接口和格式方面给开发人员和管理员提供了比以往所有的远程机制更大的选择空间。在图(2-1)中,您可以看到.NET Remoting的一个简单构架图。一旦客户端程序获得了远程对象的引用,这个远程对象将被TransparentProxy模拟成请求的目标对象而被展现出来。这个透明代理(TransparentProxy)允许实例的目标对象里的任何方法在其上被调用。一旦方法的调用被安置在这个代理之上,它将被转换成消息,并逐层传递。
图(2-1).NET Remoting 构架简图
这个消息将通过一个序列化层(the formatter 格式化程序)被转换成特定的传输格式(例如SOAP)。而后序列化的消息将到达一个传输信道并通过HTTP或者TCP这样的特定协议传送到远端进程中去。在远程服务器端,这个消息也将通过一个格式化层(formatting layer)被反向序列化成原始消息后投递到Dispatcher(发报机)。最终,Dispatcher调用目标对象的目标方法、获取返回值并按原路回送。关于这个构架的具体分析,我们将在第七章中进行。
和其他的远程构架形成对比的是,.NET Remoting允许扩展甚至完全替换很多固有层(Layers),从而自定义得去控制消息的处理。(第八、九、十章详述)。
同时,您可以在不改变任何源代码的情况下轻松切换不同层的实现方法——通过在配置文件中修改少许行来替换.NET Remoting的传输信道,一个基于二进制TCP协议的远程应用程序就可以开放另一个使用SOAP/HTTP协议的模块了。
精炼的接口定义
很多远程系统,例如DCE/RPC、RMI和J2EE都需要在服务器端手工的创建所谓的proxy/stub(代理/存根)对象。Proxy负责包装对客户机上所谓的远程对象[提示:和.NET 中的客户端代理对象的概念类似]的连接,同时负责把对所谓的存根对象的调用请求投递给真实的对象(the real object)。更痛苦的是,在大部分情况下,生成这些繁琐的架构对象的源代码必须使用一种被称为接口定义语言(Interface Definition Language)的抽象难懂的代码来编写,并被预编译为程序块的执行头部。
和这种传统的方法相比,.NET Remoting对所有的.NET远程对象使用统一的代理模型。.NET Remoting能做到这一点并不是什么难事,因为.NET框架在设计之初,就已把远程应用列入其设计思想之中,而其他的一些平台在系统设计阶段没有这种考虑,所以它们对于远程应用的支持仅是花样的翻新和在即成构架上的重新整合。
值得一提的是,.NET为我们提供的这种安逸也很有可能因为您不恰当的设计而存在潜在的问题——别担心,本书将帮助您构建正确合理的解决方案。例如,即便您并不必使用接口定义语言(IDL)来定义任何的接口,但您仍然应该定义接口,并将接口从它的实现中分离出来;当然,您可以使用任何一种或几种.NET编程语言来完成。
.NET Remoting提供了几种不同的方式来定义这些接口:
共享装配件
在这种情况下,服务器端对象的实现代码会不可避免的在客户端存在。只是在实例化时才决定到底是本地对象还是远程服务器端对象被创建。这种方式允许对实现代码的实例化和调用在本地(例如,当离线工作时)或者在服务器端(例如,连线状态下在高性能的服务器上运行科学计算)进行半透明的切换。
我们在使用传统的分布式应用程序时,不能在离线情形下进行。您必须加倍小心以避免程序出错的风险——当在客户机上错误的实例化了一个对象并把它传递给服务器(例如作为方法的参数)时,您可能会陷入严重的麻烦:无效的类型转换(InvalidCastExceptions),抑或是“由于防火墙的约束,程序不能在发布环境下正常运行”这样莫名其妙的提示。在这种情况下,客户机实际上已经鬼使神差地成为了服务器,而后续对于此对象的调用也将从“服务器”传向“客户机”!
共享接口或基类对象
这种方式是指在设计分布式程序时,您在一个独立的组件(装配件)中定义基类或是接口。这个组件将在客户机和服务器上同时使用。而真正的接口实现或是基类继承仅仅只被安置在服务器端。
后期元数据抽取
也许是最优雅的一种。其实这种方式下,您在开发服务器端应用时所做的工作和使用共享组件(第一种方式)时没有什么不同,只是稍后您需要在客户机上使用SoapSuds从编译好的组件中提取必须的信息,接口信息等就包含在其中。
SoapSuds需要一个指向服务器的URL和目标组件的名称作为参数来抽取必要的信息(接口、基类、以值传送的对象等等)。SoapSuds将这些数据放入一个新的组件中并被客户机引用。
强大的生存期管理
在分布式应用程序领域,通常有三种方法进行生存期管理。第一种方法是保有一个从客户机到服务器端的开放的网络连接(例如,使用TCP)。一旦连接终止,服务器上相应的内存将被释放。
第二种是使用DCOM方式,它使用联合的引用计数和Ping机制。在这种方式下,服务器在特定的时间间隔里接受来自客户机的消息。当在最新的时钟周期中接受不到任何的消息,服务器将释放相应的资源。
而到了Internet时代,您不可能预先知道您的客户身在何处,所以您也就不可能为客户机和服务器建立直接的TCP连接。另外,您的最终用户往往习惯于躲在防火墙之后,并只允许文本的HTTP流向它的机器。与此同时,路由器也会把服务器发向您的用户机器的所有ping请求统统驳回。
由于这些因素,.NET Remoting的生存期服务被微软的工程师们设计为可以自定义的。默认状态下,服务器上的对象将被分配给一个初始生存期,并在客户端对它请求调用时获得延长的生存期。.NET Remoting同时也允许为服务器端对象注册一个“赞助人”(sponsor),并能在生存期满时联系此“赞助人”以求延长生存期。
这两种方式的联合将造就一个可配置的生存期管理服务,它不依赖于任何的服务器和客户机的连接。[本章完]