.Net遠(yuǎn)程方法調(diào)用研究
2024-07-10 12:58:45
供稿:網(wǎng)友
 
簡(jiǎn)介
遠(yuǎn)程方法調(diào)用發(fā)展到現(xiàn)在,已經(jīng)有以下幾種框架實(shí)現(xiàn):dce/rpc,corba,dcom,mts/com+,java rmi,java ejb,web services/soap/xml-rpc,net remoting,本文主要介紹了.net遠(yuǎn)程方法調(diào)用的原理,實(shí)現(xiàn)以及與微軟com/dcom實(shí)現(xiàn)的異同點(diǎn)。
框架
microsoft .net remoting 提供了一種允許對(duì)象通過應(yīng)用程序域與另一對(duì)象進(jìn)行交互的框架。眾所周知,web服務(wù)僅僅提供了一種簡(jiǎn)單的容易理解的方法來實(shí)現(xiàn)跨平臺(tái),跨語言的交互,而dotnet remoting相對(duì)于web服務(wù)就像asp相對(duì)于cgi那樣,實(shí)現(xiàn)了一種質(zhì)的轉(zhuǎn)變。dotnet remoting提供了一個(gè)可擴(kuò)展的框架,它可以選擇不同的傳輸機(jī)制(http和tcp是內(nèi)置的),不同的編碼方式(soap以及二進(jìn)制代碼),安全設(shè)置(iis或ssl),同時(shí)提供了多種服務(wù),包括激活和生存期支持。
遠(yuǎn)程方法調(diào)用
對(duì)于遠(yuǎn)程方法調(diào)用來說,最直接的問題恐怕是:一個(gè)本地方法,推而廣之,一個(gè)本地對(duì)象,如果放在網(wǎng)絡(luò)環(huán)境中,如何傳遞這個(gè)方法的調(diào)用,返回,如何傳遞這個(gè)對(duì)象的請(qǐng)求。雖然對(duì)于應(yīng)用開發(fā)人員來說這個(gè)并不是必不可少的事,但對(duì)于我們學(xué)習(xí)分布式操作系統(tǒng)來說,我想這是應(yīng)該搞清楚的。我們知道,dcom協(xié)議也被稱為對(duì)象rpc,它建立在dce rpc協(xié)議基礎(chǔ)上,也就是說,在網(wǎng)絡(luò)傳輸這一層,它必須使用特殊的協(xié)議。另外windows rpc 機(jī)制要求熟悉的類型和使用 idl 工具的封送處理知識(shí),并向開發(fā)人員公開 rpc 客戶端和服務(wù)器存根的管理。remoting 在為 .net 提供 rpc 時(shí)要容易得多,而且由于使用簡(jiǎn)單易懂的 .net 數(shù)據(jù)類型,從而消除了早期 rpc 機(jī)制中存在的類型不匹配的情況(這是一個(gè)非常大的威脅)。配置為使用 http 或 tcp 協(xié)議,并使用 xml 編碼的 soap 或本機(jī)二進(jìn)制消息格式進(jìn)行通信。開發(fā)人員可以構(gòu)建自定義的協(xié)議(通道)或消息格式(格式化程序),并在需要時(shí)由 remoting 框架使用。服務(wù)器和客戶端組件都可以選擇端口,就象可以選擇通信協(xié)議一樣。由此帶來的一個(gè)好處是,很容易建立并運(yùn)行基本的通信。
下圖中描述了.net remoting的五要素:
代理:在client端偽裝為remote objects并轉(zhuǎn)發(fā)對(duì)remote objects的調(diào)用。
message:消息對(duì)象包含了執(zhí)行remote methods調(diào)用的必要數(shù)據(jù)參數(shù)。
message sink/channel sink:在remote調(diào)用中,message sink允許定制消息處理流程,這是.net remoting內(nèi)置的可擴(kuò)展特性。
formatter:也是message sink,用來序列化消息,已適于網(wǎng)絡(luò)傳輸,如soap。
transport channel:也是message sink,用來傳輸序列化的消息到遠(yuǎn)程進(jìn)程,如http。
當(dāng)訪問remote objects時(shí),client端application并不處理真實(shí)對(duì)象的引用,而是僅僅調(diào)用proxy對(duì)象的方法。proxy對(duì)象提供與remote objects相同的接口,偽裝成remote objects。proxy對(duì)象自己并不執(zhí)行任何方法,而是以消息對(duì)象(message object)的形式轉(zhuǎn)發(fā)每一個(gè)方法調(diào)用給.net remoting framework。
在類型支持方面,dcom提供了一套復(fù)雜的列集和散集機(jī)制,他建立在rpc的基礎(chǔ)上。由于rpc被定義為dce標(biāo)準(zhǔn)的一部分,而dce rpc定義了所有常用的數(shù)據(jù)類型的數(shù)據(jù)表達(dá)方法,即網(wǎng)絡(luò)數(shù)據(jù)表示法。為了使存根(stub)代碼和代理對(duì)象能夠正確地對(duì)參數(shù)和返回結(jié)果也進(jìn)行列集和散集,它們應(yīng)該使用一致的數(shù)據(jù)表示法ndr,以便在不同的操作系統(tǒng)環(huán)境下也能夠遠(yuǎn)程調(diào)用。
 
this figure is from the book named advanced .net remoting.
反過來,我們?cè)倏纯?net remoting 強(qiáng)大的類型操作,.net remoting 支持所有托管的類型、類、接口、枚舉、對(duì)象等,這通常被稱為“多類型保真”。這里的關(guān)鍵在于,如果客戶端和服務(wù)器組件都是在應(yīng)用程序域中運(yùn)行的 clr 托管的對(duì)象,則數(shù)據(jù)類型的互操作是不成問題的。從根本上講,我們擁有的是一個(gè)封閉的系統(tǒng),會(huì)話的兩端可以完全被理解,因此我們可以充分利用這一事實(shí),處理好用于通信的數(shù)據(jù)類型和對(duì)象。
在各種系統(tǒng)并存的情況下,我們需要考慮系統(tǒng)之間的互操作性。對(duì)于可互操作的數(shù)據(jù)類型,我們要謹(jǐn)慎處理。例如,web 服務(wù)數(shù)據(jù)類型的定義要基于 xml 架構(gòu)定義 (xsd) 關(guān)于數(shù)據(jù)類型的說明。任何可以使用 xsd 進(jìn)行描述并可以在 soap 上進(jìn)行互操作的類型都可以使用。但是,這也確實(shí)使得某些數(shù)據(jù)類型不能使用。
代理
代理對(duì)象偽裝成一個(gè)遠(yuǎn)程對(duì)象,并且向本地提供遠(yuǎn)程對(duì)象相同的接口。客戶端應(yīng)用程序與處理遠(yuǎn)程“真實(shí)”對(duì)象的應(yīng)用(包括內(nèi)存引用,指針,等等),都是通過代理對(duì)象實(shí)現(xiàn)的。代理對(duì)象當(dāng)然并不自己完成方法調(diào)用,它將請(qǐng)求作為消息對(duì)象傳給.net framework。在這一方面,.net remoting 和dcom思想上是一致的。當(dāng)某個(gè)客戶端激活一個(gè)遠(yuǎn)程對(duì)象時(shí),框架將創(chuàng)建 transparentproxy 類的一個(gè)本地實(shí)例(該類中包含所有類的列表與遠(yuǎn)程對(duì)象的接口方法)。因?yàn)?transparentproxy 類在創(chuàng)建時(shí)用 clr 注冊(cè),所以代理上的所有方法調(diào)用都被運(yùn)行時(shí)截取。這時(shí)系統(tǒng)將檢查調(diào)用,以確定其是否為遠(yuǎn)程對(duì)象的有效調(diào)用,以及遠(yuǎn)程對(duì)象的實(shí)例是否與代理位于同一應(yīng)用程序域中。如果對(duì)象在同一個(gè)應(yīng)用程序域中,則簡(jiǎn)單方法調(diào)用將被路由到實(shí)際對(duì)象;如果對(duì)象位于不同的應(yīng)用程序域中,將通過調(diào)用堆棧中的調(diào)用參數(shù)的 invoke 方法將其打包到 imessage 對(duì)象并轉(zhuǎn)發(fā)到 realproxy 類中。此類(或其內(nèi)部實(shí)現(xiàn))負(fù)責(zé)向遠(yuǎn)程對(duì)象轉(zhuǎn)發(fā)消息。transparentproxy 類和 realproxy 類都是在遠(yuǎn)程對(duì)象被激活后在后臺(tái)創(chuàng)建的,但只有 transparentproxy 返回到客戶端。在代理對(duì)象創(chuàng)建時(shí),需要引用client端的messag sink chain,sink chain的第一個(gè)sink對(duì)象的引用保存在realproxy對(duì)象的identity屬性。如下圖所示:
 
所以,在下面這段代碼調(diào)用new或者getobject獲得對(duì)象后,對(duì)象obj將會(huì)指向
transparentproxy對(duì)象。
httpchannel channel = new httpchannel();
channelservices.registerchannel(channel);
someclass obj = (someclass) activator.getobject(
type of(someclass),
"http://localhost:1234/somesao.soap");
消息
當(dāng)一次函數(shù)調(diào)用指向遠(yuǎn)程對(duì)象的引用時(shí),transparentproxy創(chuàng)建一個(gè)messagedata對(duì)象并且將它傳給realproxy對(duì)象的privateinvoke()調(diào)用,realproxy相應(yīng)地生成一個(gè)新的message對(duì)象并且以messagedata對(duì)象為參數(shù)調(diào)用其initfields()方法,message將會(huì)解析messagedata對(duì)象中地指針進(jìn)行相應(yīng)地函數(shù)調(diào)用。當(dāng)處理結(jié)束時(shí),將會(huì)返回一個(gè)包含回應(yīng)消息的imessage對(duì)象,realproxy將會(huì)調(diào)用它自己的handlereturnmessage()方法,該方法檢查參數(shù)并且調(diào)用propagateoutparameters()方法。當(dāng)處理結(jié)束后realproxy將會(huì)從它的privateinvoke()方法中返回,imessage將會(huì)返回給transparentproxy。clr保證函數(shù)返回值與其返回格式相符,所以對(duì)于應(yīng)用程序來說,它僅僅認(rèn)為這是一個(gè)一般方法的調(diào)用,返回,這正是分布式的最終要求。
可以看到,.net remoting與dcom的底層機(jī)制已經(jīng)完全不同了,dcom的一次方法調(diào)用,需要經(jīng)過列集(marshaling)和散集(unmarshaling)的處理,包括創(chuàng)建代理對(duì)象和轉(zhuǎn)載存根代碼等。但是可以看到.net做為dcom的下一代,在基本思想上和dcom是一脈相承的。在我學(xué)習(xí)到現(xiàn)在的體會(huì)看來,.net面向web服務(wù)的rpc機(jī)制的確是一大革新,讓我們看看imessage怎么進(jìn)行傳遞的:
這是一個(gè)消息穿過transport channel(也就是message sink)的實(shí)例。
首先是soap遠(yuǎn)程調(diào)用的http請(qǐng)求:
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
然后是一個(gè)對(duì)于一個(gè)遠(yuǎn)程對(duì)象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時(shí)的痛苦,不過我始終還是有疑問,微軟如果始終堅(jiān)持windows操作系統(tǒng)一統(tǒng)的路線,.net的真正跨平臺(tái)性是否真正能實(shí)現(xiàn)。
一種常見的 microsoft 理論是:如果需要在不同系統(tǒng)之間進(jìn)行互操作,應(yīng)該選擇使用開放標(biāo)準(zhǔn) (soap、xml、http) 的 web 服務(wù)方法,而使用 .net remoting 決不是一種交互的解決方案;如果各種系統(tǒng)中的所有組件都是 clr 托管的,則 .net remoting“可能”是正確的選擇。
我想我還漏了一個(gè)重要的問題:串行化(serialization)。這是實(shí)現(xiàn)跨進(jìn)程調(diào)用的關(guān)鍵技術(shù)。在com中串行化使通過列集(marshaling)和散集(unmarshaling)完成的,列集過程的復(fù)雜程度因參數(shù)類型而異,簡(jiǎn)單的入word或float只需按二進(jìn)制序列填到數(shù)據(jù)包中,復(fù)雜的參數(shù)如指針需要考慮整個(gè)指針層次,獲得所有的數(shù)據(jù)全部。散集是和列集向?qū)?yīng)的過程,遠(yuǎn)程機(jī)器的存根代碼接受到列集數(shù)據(jù)后,重新建立堆棧的過程就是散集,每次遠(yuǎn)程調(diào)用至少經(jīng)過兩次列集和兩次散集,因?yàn)榉祷刂狄残枰屑貋碓偕⒓?。com的一個(gè)問題就是在于其過于復(fù)雜的的參數(shù)傳遞問題這一套復(fù)雜的機(jī)制是很難搞明白,更何況對(duì)于代碼的定制了。.net remoting對(duì)于串行化的處理是通過一個(gè)formatter來完成的,.net remoting框架給你提供了兩個(gè)缺省的formatters,soapformatter和binaryformatter,它們都能通過http或tcp進(jìn)行傳輸。在消息完成了imessagesink對(duì)象的預(yù)處理鏈后,它將通過syncprocessmessage()到達(dá)formatter。在客戶端,soapclientformattersink將imessage傳給serializemessage()方法。這個(gè)函數(shù)設(shè)置transportheaders,請(qǐng)求它的nextsink(httpclienttransportsink ),它將創(chuàng)建一個(gè)chunkedmemorystream并且傳給channel sink.。真正的串行化由corechannel.serializesoapmessage()開始,它建立一個(gè)soapformatter,并且調(diào)用其serialize()方法,下面是一個(gè)對(duì)于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比較更加清晰,易懂,并且也確實(shí)易于開發(fā),例如我們可以實(shí)現(xiàn)一個(gè)壓縮的sink。下面是開發(fā)一個(gè)compressionsink的骨架例程,從這來看,com實(shí)在是望塵莫及。
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
}
}
}
小結(jié)
以上就是我?guī)滋靵韺?duì).net remoting的學(xué)習(xí)所得。我的學(xué)習(xí)主要集中在.net remoting的遠(yuǎn)程方法傳遞這方面,因?yàn)槲矣X得這才是rpc最關(guān)鍵的技術(shù)。從幾天的學(xué)習(xí)所得,可以看出.net framework的確是一種技術(shù),思想都非常先進(jìn)的框架。我唯一的懷疑在于微軟所謂的平臺(tái)無關(guān)性,這方面,java也許永遠(yuǎn)勝出一籌。
這篇文章0.1版放在我的blog上 http://blog.csdn.net/drizt/