简介
远程方法调用发展到现在,已经有以下几种框架实现:DCE/RPC,CORBA,DCOM,MTS/COM+,Java RMI,Java EJB,Web Services/SOAP/XML-RPC,NET Remoting,本文主要介绍了.NET远程方法调用的原理,实现以及与微软COM/DCOM实现的异同点。
框架
Microsoft .NET Remoting 提供了一种允许对象通过应用程序域与另一对象进行交互的框架。众所周知,Web服务仅仅提供了一种简单的容易理解的方法来实现跨平台,跨语言的交互,而DotNet Remoting相对于Web服务就像Asp相对于CGI那样,实现了一种质的转变。DotNet Remoting提供了一个可扩展的框架,它可以选择不同的传输机制(HTTP和TCP是内置的),不同的编码方式(SOAP以及二进制代码),安全设置(IIS或SSL),同时提供了多种服务,包括激活和生存期支持。
远程方法调用
对于远程方法调用来说,最直接的问题恐怕是:一个本地方法,推而广之,一个本地对象,如果放在网络环境中,如何传递这个方法的调用,返回,如何传递这个对象的请求。虽然对于应用开发人员来说这个并不是必不可少的事,但对于我们学习分布式操作系统来说,我想这是应该搞清楚的。我们知道,DCOM协议也被称为对象RPC,它建立在DCE RPC协议基础上,也就是说,在网络传输这一层,它必须使用特殊的协议。另外Windows RPC 机制要求熟悉的类型和使用 IDL 工具的封送处理知识,并向开发人员公开 RPC 客户端和服务器存根的管理。Remoting 在为 .NET 提供 RPC 时要容易得多,而且由于使用简单易懂的 .NET 数据类型,从而消除了早期 RPC 机制中存在的类型不匹配的情况(这是一个非常大的威胁)。配置为使用 HTTP 或 TCP 协议,并使用 XML 编码的 SOAP 或本机二进制消息格式进行通信。开发人员可以构建自定义的协议(通道)或消息格式(格式化程序),并在需要时由 Remoting 框架使用。服务器和客户端组件都可以选择端口,就象可以选择通信协议一样。由此带来的一个好处是,很容易建立并运行基本的通信。
下图中描述了.Net Remoting的五要素:
代理:在Client端伪装为Remote Objects并转发对Remote Objects的调用。
Message:消息对象包含了执行Remote Methods调用的必要数据参数。
Message Sink/Channel Sink:在Remote调用中,Message Sink允许定制消息处理流程,这是.Net Remoting内置的可扩展特性。
Formatter:也是Message Sink,用来序列化消息,已适于网络传输,如SOAP。
Transport Channel:也是Message Sink,用来传输序列化的消息到远程进程,如HTTP。
当访问Remote Objects时,Client端application并不处理真实对象的引用,而是仅仅调用Proxy对象的方法。Proxy对象提供与Remote Objects相同的接口,伪装成Remote Objects。Proxy对象自己并不执行任何方法,而是以消息对象(Message Object)的形式转发每一个方法调用给.Net Remoting Framework。
在类型支持方面,DCOM提供了一套复杂的列集和散集机制,他建立在RPC的基础上。由于RPC被定义为DCE标准的一部分,而DCE RPC定义了所有常用的数据类型的数据表达方法,即网络数据表示法。为了使存根(stub)代码和代理对象能够正确地对参数和返回结果也进行列集和散集,它们应该使用一致的数据表示法NDR,以便在不同的操作系统环境下也能够远程调用。
This figure is from the book named Advanced .Net Remoting.
反过来,我们再看看.NET Remoting 强大的类型操作,.Net Remoting 支持所有托管的类型、类、接口、枚举、对象等,这通常被称为“多类型保真”。这里的关键在于,如果客户端和服务器组件都是在应用程序域中运行的 CLR 托管的对象,则数据类型的互操作是不成问题的。从根本上讲,我们拥有的是一个封闭的系统,会话的两端可以完全被理解,因此我们可以充分利用这一事实,处理好用于通信的数据类型和对象。
在各种系统并存的情况下,我们需要考虑系统之间的互操作性。对于可互操作的数据类型,我们要谨慎处理。例如,Web 服务数据类型的定义要基于 XML 架构定义 (XSD) 关于数据类型的说明。任何可以使用 XSD 进行描述并可以在 SOAP 上进行互操作的类型都可以使用。但是,这也确实使得某些数据类型不能使用。
代理
代理对象伪装成一个远程对象,并且向本地提供远程对象相同的接口。客户端应用程序与处理远程“真实”对象的应用(包括内存引用,指针,等等),都是通过代理对象实现的。代理对象当然并不自己完成方法调用,它将请求作为消息对象传给.NET Framework。在这一方面,.Net Remoting 和DCOM思想上是一致的。当某个客户端激活一个远程对象时,框架将创建 TransparentProxy 类的一个本地实例(该类中包含所有类的列表与远程对象的接口方法)。因为 TransparentProxy 类在创建时用 CLR 注册,所以代理上的所有方法调用都被运行时截取。这时系统将检查调用,以确定其是否为远程对象的有效调用,以及远程对象的实例是否与代理位于同一应用程序域中。如果对象在同一个应用程序域中,则简单方法调用将被路由到实际对象;如果对象位于不同的应用程序域中,将通过调用堆栈中的调用参数的 Invoke 方法将其打包到 IMessage 对象并转发到 RealProxy 类中。此类(或其内部实现)负责向远程对象转发消息。TransparentProxy 类和 RealProxy 类都是在远程对象被激活后在后台创建的,但只有 TransparentProxy 返回到客户端。在代理对象创建时,需要引用Client端的Messag Sink Chain,Sink Chain的第一个Sink对象的引用保存在RealProxy对象的Identity属性。如下图所示:
所以,在下面这段代码调用new或者GetObject获得对象后,对象obj将会指向
TransparentProxy对象。
HttpChannel channel = new HttpChannel();
ChannelServices.RegisterChannel(channel);
SomeClass obj = (SomeClass) Activator.GetObject(
type of(SomeClass),
"http://localhost:1234/SomeSAO.soap");
消息
当一次函数调用指向远程对象的引用时,TransparentProxy创建一个MessageData对象并且将它传给RealProxy对象的PrivateInvoke()调用,RealProxy相应地生成一个新的Message对象并且以MessageData对象为参数调用其InitFields()方法,Message将会解析MessageData对象中地指针进行相应地函数调用。当处理结束时,将会返回一个包含回应消息的IMessage对象,RealProxy将会调用它自己的HandleReturnMessage()方法,该方法检查参数并且调用PropagateOutParameters()方法。当处理结束后RealProxy将会从它的PrivateInvoke()方法中返回,IMessage将会返回给TransparentProxy。CLR保证函数返回值与其返回格式相符,所以对于应用程序来说,它仅仅认为这是一个一般方法的调用,返回,这正是分布式的最终要求。
可以看到,.NET Remoting与DCOM的底层机制已经完全不同了,DCOM的一次方法调用,需要经过列集(marshaling)和散集(unmarshaling)的处理,包括创建代理对象和转载存根代码等。但是可以看到.NET做为DCOM的下一代,在基本思想上和DCOM是一脉相承的。在我学习到现在的体会看来,.NET面向Web服务的RPC机制的确是一大革新,让我们看看IMessage怎么进行传递的:
这是一个消息穿过Transport Channel(也就是Message Sink)的实例。
首先是SOAP远程调用的HTTP请求:
POST /MyRemoteObject.soap HTTP/1.1
User-Agent: Mozilla/4.0+(compatible; MSIE 6.0; Windows 5.0.2195.0; MS .NET
Remoting;
MS .NET CLR 1.0.2914.16 )
SOAPAction:
"http://schemas.microsoft.com/clr/nsassem/General.BaseRemoteObject/General#
setValue"
Content-Type: text/xml; charset="utf-8"
Content-Length: 510
Expect: 100-continue
Connection: Keep-Alive
Host: localhost
然后是一个对于一个远程对象setValue(int):
POST /MyRemoteObject.soap HTTP/1.1
User-Agent: Mozilla/4.0+(compatible; MSIE 6.0; Windows 5.0.2195.0; MS .NET
Remoting;
MS .NET CLR 1.0.2914.16 )
SOAPAction:
"http://schemas.microsoft.com/clr/nsassem/General.BaseRemoteObject/General#
setValue"
Content-Type: text/xml; charset="utf-8"
Content-Length: 510
Expect: 100-continue
Connection: Keep-Alive
Host: localhost
<SOAP-ENV:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:i2=
"http://schemas.microsoft.com/clr/nsassem/General.BaseRemoteObject/General"
<SOAP-ENV:Body
<i2:setValue id="ref-1"
<newval4