簡介
Microsoft? NET Remoting 提供了一種允許對象通過應用程序域與另一對象進行交互的框架這種框架提供了多種服務包括激活和生存期支持以及負責與遠程應用程序進行消息傳輸的通訊通道格式化程序用於在消息通過通道傳輸之前對其進行編碼和解碼應用程序可以在注重性能的場合使用二進制編碼在需要與其他遠程處理框架進行交互的場合使用 XML 編碼在從一個應用程序域向另一個應用程序域傳輸消息時所有的 XML 編碼都使用 SOAP 協議出於安全性方面的考慮遠程處理提供了大量掛鉤使得在消息流通過通道進行傳輸之前安全接收器能夠訪問消息和序列化流
通常如果沒有底層框架的支持管理遠程對象的生存期會非常麻煩NET Remoting 提供了許多可供選擇的生存期模型這些模型分為兩個類別:
客戶端激活對象
服務器激活對象
客戶端激活對象受基於租用的生存期管理器的控制這種管理器確保了租用期滿時對象可被回收而對於服務器激活對象開發人員則可以選擇單一調用模式或單一元素模式
遠程對象
任何遠程處理框架的主要目的之一就是要提供必要的基礎結構以便隱藏遠程對象調用方法和返回結果的復雜性任何位於調用方應用程序域之外的對象即使在同一台計算機上執行也會被認為是遠程對象在應用程序域內部原始數據類型按數值傳遞而所有的對象按引用傳遞因為本地對象引用僅在創建對象的應用程序域內有效所以它們不能以這種方式傳遞到遠程方法調用或從遠程方法調用返回所有必須跨越應用程序域的本地對象都必須按數值來傳遞並且應該用 [serializable] 自定義屬性作標記否則它們必須實現 ISerializable 接口對象作為參數傳遞時框架將該對象序列化並傳輸到目標應用程序域對象將在該目標應用程序域中被重新構造無法序列化的本地對象將不能傳遞到其他應用程序域中因而也不能遠程處理
通過從 MarshalByRefObject 導出對象您可以使任一對象變為遠程對象當某個客戶端激活一個遠程對象時它將接收到該遠程對象的代理對該代理的所有操作都被適當地重新定向使遠程處理基礎結構能夠正確截取和轉發調用盡管這種重新定向對性能有一些影響但 JIT 編譯器和執行引擎 (EE) 已經優化可以在代理和遠程對象駐留在同一個應用程序域中時防止不必要的性能損失如果代理和遠程對象不在同一個應用程序域中則堆棧中的所有方法調用參數會被轉換為消息並被傳輸到遠程應用程序域這些消息將在該遠程應用程序域中被轉換為原來的堆棧幀同時該方法調用也會被調用從方法調用中返回結果時也使用同一過程
代理對象
代理對象是在客戶端激活遠程對象時創建的作為遠程對象的代表代理對象確保對代理進行的所有調用都能夠轉發到正確的遠程對象實例為了准確理解代理對象的工作方式我們需要更深入地研究它們當某個客戶端激活一個遠程對象時框架將創建 TransparentProxy 類的一個本地實例(該類中包含所有類的列表與遠程對象的接口方法)因為 TransparentProxy 類在創建時用 CLR 注冊所以代理上的所有方法調用都被運行時截取這時系統將檢查調用以確定其是否為遠程對象的有效調用以及遠程對象的實例是否與代理位於同一應用程序域中如果對象在同一個應用程序域中則簡單方法調用將被路由到實際對象;如果對象位於不同的應用程序域中將通過調用堆棧中的調用參數的 Invoke 方法將其打包到 IMessage 對象並轉發到 RealProxy 類中此類(或其內部實現)負責向遠程對象轉發消息TransparentProxy 類和 RealProxy 類都是在遠程對象被激活後在後台創建的但只有 TransparentProxy 返回到客戶端
要更好地理解這些代理對象我們需要簡要介紹一下 ObjRef激活一節中有關於 ObjRef 的詳細說明以下方案簡要說明了 ObjRef 與這兩個代理類的關聯方式但請注意這只是關於該進程的一個極其概括的說明;根據對象是客戶端激活對象還是服務器激活對象以及它們是單一元素對象還是單一調用對象該進程會有所不同
遠程對象注冊在遠程計算機的應用程序域中遠程對象被封送以生成 ObjRefObjRef 包含了從網絡上的任意位置定位和訪問遠程對象所需的所有信息包括:類的增強名稱類的層次結構(其父類)類實現的所有接口的名稱對象 URI 和所有已注冊的可用通道的詳細信息在接收到對某個遠程對象的請求時遠程處理框架使用對象 URI 來檢索為該對象創建的 ObjRef 實例
客戶端通過調用 new 或某個 Activator 函數(例如 CreateInstance)來激活遠程對象對於服務器激活對象遠程對象的 TransparentProxy 將在客戶端應用程序域中生成並返回到客戶端這時不執行任何遠程調用只有在客戶端調用遠程對象的某個方法時該遠程對象才會被激活此方案明顯不適合客戶端激活對象因為客戶端希望框架只在得到請求時才激活對象當客戶端調用某個激活方法時客戶端上會創建一個激活代理並且將使用 URL 和對象 URI 作為終結點在服務器的遠程激活器上初始化一個遠程調用遠程激活器激活該對象然後 ObjRef 流向客戶端並被取消封送以生成一個返回給客戶端的 TransparentProxy
取消封送的過程中會分析 ObjRef 以提取遠程對象的方法信息同時還會創建 TransparentProxy 和 RealProxy 對象在用 CLR 注冊 TransparentProxy 之前分析後的 ObjRef 內容會被添加到 TransparentProxy 的內部表中
TransparentProxy 是一種無法替代和擴展的內部類而 RealProxy 和 ObjRef 類則屬於公共類可以在必要時進行擴展和自定義因為 RealProxy 類能夠處理遠程對象的所有函數調用所以它是執行負載平衡等操作的理想方法調用 Invoke 時從 RealProxy 導出的類可以獲得網絡中服務器的負載信息並將該調用路由到適當的服務器簡單地為所需的 ObjectURI 從通道請求一個 MessageSink並調用 SyncProcessMessage 或 AsyncProcessMessage 以將該調用轉發至所需的遠程對象當調用返回時通過調用 RemotingServices 類的 PropagateMessageToProxy 將返回參數推回到堆棧中
下面的代碼片斷顯示了如何使用導出的 RealProxy 類
MyRealProxy proxy = new MyRealProxy(typeof(Foo));
Foo obj = (Foo)proxyGetTransparentProxy();
int result = objCallSomeMethod();
上例中獲取的 TransparentProxy 可以被轉發到另一個應用程序域中當第二個客戶端試圖調用代理上的某個方法時遠程處理框架會嘗試創建 MyRealProxy 類的實例並且如果程序集可用所有的調用都會路由至此實例如果程序集不可用調用會路由至默認的遠程 RealProxy
通過為默認的 ObjRef 屬性 TypeInfoEnvoyInfo 和 ChannelInfo 提供替代可以很容易地自定義 ObjRef下列代碼顯示了如何進行自定義:
public class ObjRef {
public virtual IRemotingTypeInfo TypeInfo
{
get { return typeInfo;}
set { typeInfo = value;}
}
public virtual IEnvoyInfo EnvoyInfo
{
get { return envoyInfo;}
set { envoyInfo = value;}
}
public virtual IChannelInfo ChannelInfo
{
get { return channelInfo;}
set { channelInfo = value;}
}
}
通道
通道用於在遠程對象之間傳輸消息當客戶端調用某個遠程對象上的方法時與該調用相關的參數以及其他詳細信息會通過通道傳輸到遠程對象調用的任何結果都會以同樣的方式返回給客戶端客戶端可以選擇服務器中注冊的任一通道以實現與遠程對象之間的通訊因此開發人員可以自由選擇最適合需要的通道當然也可以自定義任何現有的通道或創建使用其他通訊協議的新通道通道選擇遵循以下規則:
在能夠調用遠程對象之前遠程處理框架必須至少注冊一個通道通道注冊必須在對象注冊之前進行
通道按應用程序域注冊一個進程中可以有多個應用程序域當進程結束時該進程注冊的所有通道將被自動清除
多次注冊偵聽同一端口的通道是非法的即使通道按應用程序域注冊同一計算機上的不同應用程序域也不能注冊偵聽同一端口的通道
客戶端可以使用任何已注冊的通道與遠程對象通訊當客戶端試圖連接至某個遠程對象時遠程處理框架會確保該對象連接至正確的通道客戶端負責在嘗試與遠程對象通訊之前調用 ChannelService 類的 RegisterChannel
所有的通道都由 IChannel 導出並根據通道的用途實現 IChannelReceiver 或 IchannelSender大多數通道既實現了接收器接口又實現了發送器接口使它們可以在兩個方向上通訊當客戶端調用代理上的某個方法時遠程處理框架會截取該調用並將其轉為要發送到 RealProxy 類(或一個實現 RealProxy 類的實例)的消息RealProxy 將消息轉發到消息接收器以進行處理消息接收器負責與遠程對象注冊的通道之間建立連接並通過通道(在不同的應用程序域)將消息從調度位置傳輸到遠程對象本身激活了一個遠程對象後客戶端會通過調用選定通道上的 CreateMessageSink 來選擇通道並從其上檢索能夠與遠程對象通訊的消息接收器
遠程處理框架的一個容易混淆的方面是遠程對象和通道之間的關系例如如果 SingleCall 遠程對象只在被調用時才激活那麼該對象如何偵聽要連接的客戶端?
部分答案在於這樣一個事實:遠程對象並不擁有自己的通道而是共享通道作為遠程對象宿主的服務器應用程序必須注冊要通過遠程處理框架公開的對象以及所需的通道注冊後的通道會自動開始在指定的端口偵聽客戶請求注冊遠程對象後會為該對象創建一個 ObjRef 並將其存儲在表中當通道上傳來一個請求時遠程處理框架會檢查該消息以確定目標對象同時檢查對象引用表以定位表中的引用如果找到了對象引用將從表中檢索框架目標對象或在必要時將其激活然後框架將調用轉發至該對象對於同步調用在消息調用期間會一直維持來自客戶端的連接因為每個客戶端連接都在自己的線程上處理所以一個通道可以同時服務於多個客戶端
生成商務應用時安全性是一個重要問題要滿足商務要求開發人員必須能給遠程方法調用添加諸如授權或加密等安全特性為了實現這一目標開發人員可以自定義通道使其能夠對與遠程對象之間的實際消息傳輸機制進行控制在傳輸到遠程應用程序之前所有的消息都必須流過 SecuritySinkTransportSink 和 FormatterSink且這些消息傳遞到遠程應用程序後會以相反次序流過同樣的接收器
HTTP 通道
HTTP 通道使用 SOAP 協議與遠程對象傳輸消息所有的消息流過 SOAP 格式化程序時都被轉換為 XML 格式且被序列化所需的 SOAP 頭也會被添加到該流中您也可以指定能夠生成二進制數據流的二進制格式化程序然後數據流會使用 HTTP 協議傳輸到目標 URI
TCP 通道
TCP 通道使用二進制格式化程序將所有的消息序列化為二進制流並使用 TCP 協議將其傳輸到目標 URI
激活
遠程處理框架支持遠程對象的服務器激活和客戶端激活不需要遠程對象在方法調用之間維護任何狀態時一般使用服務器激活服務器激活也適用於多個客戶端調用方法位於同一對象實例上且對象在函數調用之間維持狀態的情況另一方面客戶端激活對象從客戶端實例化並且客戶端通過使用基於租用的專用系統來管理遠程對象的生存期
在可以接受客戶端的訪問之前所有的遠程對象都必須用遠程處理框架注冊對象注冊一般由宿主應用程序來完成宿主應用程序將啟動使用 ChannelServices 注冊一個或多個通道使用 RemotingServices 注冊一個或多個遠程對象然後等待被終止請注意已注冊的通道和對象只有在用來注冊它們的進程活動時才可以使用如果退出了該進程則會自動從遠程處理服務中刪除它注冊的所有通道和對象在框架中注冊遠程對象時需要以下四項信息:
包含類的程序集名稱
遠程對象的類型名稱
客戶端定位對象時將使用的對象 URI
服務器激活所需的對象模式該模式可以是 SingleCall也可以是 Singleton
遠程對象可以通過下列兩種方式注冊:調用 RegisterWellKnownType將上述信息作為參數傳遞;或將上述信息存儲在配置文件中然後調用 ConfigureRemoting 並將該配置文件的名稱作為參數傳遞以上兩種方法執行的功能相同因此您可以使用它們中的任意一種來注冊遠程對象當然後一種方法更方便些因為無需重新編譯宿主應用程序即可改變配置文件的內容以下代碼片斷顯示了如何將 HelloService 類注冊為 SingleCall 遠程對象
RemotingServicesRegisterWellKnownType(
server
SamplesHelloServer
SayHello
WellKnownObjectModeSingleCall);
其中server是程序集的名稱HelloServer 是類的名稱SayHello 是對象 URI
注冊了遠程對象後框架將為該對象創建一個對象引用然後從程序集中提取與該對象相關的必要元數據隨後這一信息將與 URI 和程序集名稱一起存儲在對象引用中(該對象引用將被寫入一個用於跟蹤已注冊遠程對象的遠程處理框架表中)請注意除了在客戶端試圖調用對象上的某個方法或從客戶端激活對象時以外注冊進程不會實例化遠程對象自身
現在任何知道該對象 URI 的客戶端都可以使用 ChannelServices 注冊通道並調用 newGetObject 或 CreateInstance 激活對象從而獲得該對象的一個代理以下代碼片斷顯示了該操作的示例:
ChannelServicesRegisterChannel(new TCPChannel);
HelloServer obj = (HelloServer)ActivatorGetObject(
typeof(SamplesHelloServer) tcp://localhost:/SayHello);
其中tcp://localhost:/SayHello表示我們希望在端口 上使用 TCP 協議連接到位於 SayHello 終結點的遠程對象在編譯該客戶端代碼時編譯器明顯會要求關於 HelloServer 類的類型信息該信息可以通過以下方式之一來提供:
提供對 HelloService 類所在程序集的引用
將遠程對象拆分為實現和接口類並在編譯客戶端時引用這些接口
使用 SOAPSUDS 工具直接從終結點提取所需的元數據此工具將連接至所提供的終結點提取元數據然後生成可用於編譯客戶端的程序集或源代碼
GetObject 或 new 可用於服務器激活對象請注意使用這兩個調用時不會實例化對象實際上不會生成任何網絡調用框架從元數據獲得了創建代理所需的足夠信息但並未連接到遠程對象上只有在客戶端調用代理上的某個方法時才會建立網絡連接當調用抵達服務器時框架將從消息中提取 URI檢查遠程處理框架表以便定位與 URI 匹配的對象引用然後在必要時將對象實例化並將方法調用轉發至對象如果將對象注冊為 SingleCall則完成方法調用後該對象會取消每次調用一個方法時都會創建一個新的實例GetObject 和 new 之間的唯一差別在於前者允許指定 URL 作為參數而後者從配置中獲得 URL
CreateInstance 或 new 可用於客戶端激活對象兩者都允許使用帶參數的構造函數來實例化對象客戶端激活對象的生存期由遠程處理框架提供的租用服務控制對象租用的內容在下一節中說明
對象的租用生存期
每個應用程序域都包含一個用於管理其租用情況的租用管理器所有的租用都會被定期檢查以確定租用是否已過期如果租用過期則會調用該租用的一個或多個發起者使它們有機會更新租用如果所有的發起者都不准備更新租用則租用管理器會刪除該租用並將該對象作為垃圾回收租用管理器按照剩余租用時間的順序維護租用列表剩余時間最短的租用排在列表的頂端
租用可以實現 ILease 接口並存儲一個屬性集合用於確定更新的策略和方法您也可以使用調用來更新租用每次調用遠程對象上的方法時租用時間都會設置為目前 LeaseTime 最大值加上 RenewOnCallTimeLeaseTime 即將過期時發起者會被要求更新租用因為我們有時會遇上網絡不穩定所以可能會找不到租用發起者為了確保不在服務器上留下無效對象每個租用都帶有一個 SponsorshipTimeout該值指定了租用終止之前等待租用發起者回復的時間長度如果 SponsershipTimeout 為零CurrentLeaseTime 會被用於確定租用的過期時間如果 CurrentLeaseTime 的值為零則租用不會過期配置或 API 可用於替代 InitialLeaseTimeSponsorshipTimeout 和 RenewOnCallTime 的默認值
租用管理器維護著一個按發起時間從大到小存儲的發起者列表(它們實現 ISponsor 接口)需要調用發起者以更新租用時間時租用管理器會從列表的頂部開始向一個或多個發起者要求更新租用時間列表頂部的發起者表示其以前請求的租用更新時間最長如果發起者沒有在 SponsorshipTimeOut 時間段內響應則它會被從列表中刪除通過調用 GetLifetimeService 並將對象租用作為參數即可以獲得該對象租用該調用是 RemotingServices 類的一個靜態方法如果對象在應用程序域內部則該調用的參數是對象的本地引用且返回的租用也是該租用的本地引用如果對象是遠程的則代理會作為一個參數傳遞且返回給調用方的是租用的透明代理
對象能夠提供自己的租用並控制自己的生存期它們通過替代 MarshalByRefObject 上的 InitializeLifetimeService 方法來完成該操作如下所示:
public class Foo : MarshalByRefObject {
public override Object InitializeLifetimeService()
{
ILease lease = (ILease)baseInitializeLifetimeService();
if (leaseCurrentState == LeaseStateInitial) {
leaseInitialLeaseTime = TimeSpanFromMinutes();
leaseSponsorshipTimeout = TimeSpanFromMinutes();
leaseRenewOnCallTime = TimeSpanFromSeconds();
}
return lease;
}
}
只有當租用處於初始狀態時才可以更改租用屬性InitializeLifetimeService 的實現通常調用基類的相應方法來檢索遠程對象的現有租用如果在此之前從未對該對象封送過則返回的租用會處於其初始狀態且可以設置租用屬性一旦封送了對象則租用會從初始狀態變為激活狀態並忽略任何初始化租用屬性的嘗試(但有一種情況例外)激活遠程對象時將調用 InitializeLifetimeService通過激活調用可以提供一個租用發起者的列表而且當租用處於激活狀態時可以隨時將其他發起者添加到列表中
可以下列方式延長租用時間:
客戶端可以調用 Lease 類上的 Renew 方法
租用可以向某個發起者請求 Renewal
當客戶端調用對象上的某個方法時RenewOnCall 值會自動更新租用
一旦租用過期其內部狀態會由 Active 變為 Expired且不再對發起者進行任何調用對象也會被作為垃圾回收一般情況下如果發起者分散在 Web 上或位於某個防火牆的後面遠程對象回叫發起者時會遇到困難因此發起者不必與客戶端處於同一位置只要遠程對象能夠訪問得到它可以為網絡上的任意位置
通過租用來管理遠程對象的生存期可以作為引用計數的一種替代方法因為當網絡連接的性能不可靠時引用計數會顯得復雜和低效盡管有人會堅持認為遠程對象的生存期比所需的時間要長但與引用計數和連接客戶相比租用降低了網絡的繁忙程度將會成為一種非常受歡迎的解決方案
總結
要提供完美的能夠滿足大多數商務應用需求的遠程處理框架即使能夠做到也必然會非常困難Microsoft 提供了能夠根據需要進行擴展和自定義的框架在正確的方向上邁出了關鍵的一步
From:http://tw.wingwit.com/Article/program/net/201311/12452.html