.Net遠程方法調用研究

- 中國WEB開發者網絡 (http://www.webasp.net)
-- 技術教程 (http://www.webasp.net/article/)
--- .Net遠程方法調用研究 (http://www.webasp.net/article/18/17295.htm)
-- 作者:未知
-- 發佈日期: 2005-04-01
簡介
遠程方法調用發展到現在,已經有以下幾種框架實現: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">

<newval>42</newval>

</i2:setValue>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>





看起來是多麼美妙的形式啊,完全擺脫了分析RPC時的痛苦,不過我始終還是有疑問,微軟如果始終堅持windows操作系統一統的路線,.NET的真正跨平台性是否真正能實現。

一種常見的 Microsoft 理論是:如果需要在不同系統之間進行互操作,應該選擇使用開放標準 (SOAP、XML、HTTP) 的 Web 服務方法,而使用 .NET Remoting 決不是一種交互的解決方案;如果各種系統中的所有組件都是 CLR 托管的,則 .NET Remoting「可能」是正確的選擇。

我想我還漏了一個重要的問題:串行化(Serialization)。這是實現跨進程調用的關鍵技術。在COM中串行化使通過列集(marshaling)和散集(unmarshaling)完成的,列集過程的複雜程度因參數類型而異,簡單的入WORD或float只需按二進制序列填到數據包中,複雜的參數如指針需要考慮整個指針層次,獲得所有的數據全部。散集是和列集向對應的過程,遠程機器的存根代碼接受到列集數據後,重新建立堆棧的過程就是散集,每次遠程調用至少經過兩次列集和兩次散集,因為返回值也需要列集回來再散集。COM的一個問題就是在於其過於複雜的的參數傳遞問題這一套複雜的機制是很難搞明白,更何況對於代碼的定制了。.NET Remoting對於串行化的處理是通過一個formatter來完成的,.NET Remoting框架給你提供了兩個缺省的formatters,SoapFormatter和BinaryFormatter,它們都能通過HTTP或TCP進行傳輸。在消息完成了ImessageSink對象的預處理鏈後,它將通過SyncProcessMessage()到達formatter。在客戶端,SoapClientFormatterSink將IMessage傳給SerializeMessage()方法。這個函數設置TransportHeaders,請求它的NextSink(HttpClientTransportSink ),它將創建一個ChunkedMemoryStream並且傳給channel sink.。真正的串行化由CoreChannel.SerializeSoapMessage()開始,它建立一個SoapFormatter,並且調用其Serialize()方法,下面是一個對於obj.setValue(42)方法的SOAP輸出

<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.BaseRemoteObj ***

ect/General">

<SOAP-ENV:Body>

<i2:setValue id="ref-1">

<newval>42</newval>

</i2:setValue>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>





這種方式與COM比較更加清晰,易懂,並且也確實易於開發,例如我們可以實現一個壓縮的Sink。下面是開發一個CompressionSink的骨架例程,從這來看,COM實在是望塵莫及。

using System;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Messaging;

using System.IO;




namespace CompressionSink

{

public class CompressionClientSink: BaseChannelSinkWithProperties,

IClientChannelSink

{

private IClientChannelSink _nextSink;




public CompressionClientSink(IClientChannelSink next)

{

_nextSink = next;

}

public IClientChannelSink NextChannelSink

{

get {

return _nextSink;

}

}




public void AsyncProcessRequest(IClientChannelSinkStack sinkStack,

IMessage msg,

ITransportHeaders headers,

Stream stream)

{

// TODO: Implement the pre-processing




sinkStack.Push(this,null);

_nextSink.AsyncProcessRequest(sinkStack,msg,headers,stream);

}

public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack,

object state,

ITransportHeaders headers,

Stream stream)

{

// TODO: Implement the post-processing




sinkStack.AsyncProcessResponse(headers,stream);

}




public Stream GetRequestStream(IMessage msg,

ITransportHeaders headers)

{

return _nextSink.GetRequestStream(msg, headers);

}




public void ProcessMessage(IMessage msg,

ITransportHeaders requestHeaders,

Stream requestStream,

out ITransportHeaders responseHeaders,

out Stream responseStream)

{

// TODO: Implement the pre-processing




_nextSink.ProcessMessage(msg,

requestHeaders,

requestStream,

out responseHeaders,

out responseStream);




// TODO: Implement the post-processing




}

}

}

小結
以上就是我幾天來對.NET Remoting的學習所得。我的學習主要集中在.NET Remoting的遠程方法傳遞這方面,因為我覺得這才是RPC最關鍵的技術。從幾天的學習所得,可以看出.NET FrameWork的確是一種技術,思想都非常先進的框架。我唯一的懷疑在於微軟所謂的平台無關性,這方面,JAVA也許永遠勝出一籌。





這篇文章0.1版放在我的blog上 http://blog.csdn.net/drizt/




webasp.net