Microsoft .NET Remoting 框架技術

iDotNetSpace發表於2010-03-17

簡介

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 與這兩個代理類的關聯方式。但請注意,這只是關於該程式的一個極其概括的說明;根據物件是客戶端啟用物件還是伺服器啟用物件,以及它們是單一元素物件還是單一呼叫物件,該程式會有所不同。

遠端物件註冊在遠端計算機的應用程式域中。遠端物件被封送以生成 ObjRef。ObjRef 包含了從網路上的任意位置定位和訪問遠端物件所需的所有資訊,包括:類的增強名稱、類的層次結構(其父類)、類實現的所有介面的名稱、物件 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 bj = (Foo)proxy.GetTransparentProxy();
int result = obj.CallSomeMethod();
上例中獲取的 TransparentProxy 可以被轉發到另一個應用程式域中。當第二個客戶端試圖呼叫代理上的某個方法時,遠端處理框架會嘗試建立 MyRealProxy 類的例項,並且如果程式集可用,所有的呼叫都會路由至此例項。如果程式集不可用,呼叫會路由至預設的遠端 RealProxy。

通過為預設的 ObjRef 屬性 TypeInfo、EnvoyInfo 和 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 並將其儲存在表中。當通道上傳來一個請求時,遠端處理框架會檢查該訊息以確定目標物件,同時檢查物件引用表以定位表中的引用。如果找到了物件引用,將從表中檢索框架目標物件或在必要時將其啟用,然後框架將呼叫轉發至該物件。對於同步呼叫,在訊息呼叫期間會一直維持來自客戶端的連線。因為每個客戶端連線都在自己的執行緒上處理,所以一個通道可以同時服務於多個客戶端。

生成商務應用時,安全性是一個重要問題。要滿足商務要求,開發人員必須能給遠端方法呼叫新增諸如授權或加密等安全特性。為了實現這一目標,開發人員可以自定義通道,使其能夠對與遠端物件之間的實際訊息傳輸機制進行控制。在傳輸到遠端應用程式之前,所有的訊息都必須流過 SecuritySink、TransportSink 和 FormatterSink,且這些訊息傳遞到遠端應用程式後會以相反次序流過同樣的接收器。

HTTP 通道

HTTP 通道使用 SOAP 協議與遠端物件傳輸訊息。所有的訊息流過 SOAP 格式化程式時都被轉換為 XML 格式且被序列化,所需的 SOAP 頭也會被新增到該流中。您也可以指定能夠生成二進位制資料流的二進位制格式化程式。然後,資料流會使用 HTTP 協議傳輸到目標 URI。

TCP 通道

TCP 通道使用二進位制格式化程式將所有的訊息序列化為二進位制流,並使用 TCP 協議將其傳輸到目標 URI。


--------------------------------------------------------------------------------

啟用

遠端處理框架支援遠端物件的伺服器啟用和客戶端啟用。不需要遠端物件在方法呼叫之間維護任何狀態時,一般使用伺服器啟用。伺服器啟用也適用於多個客戶端呼叫方法位於同一物件例項上、且物件在函式呼叫之間維持狀態的情況。另一方面,客戶端啟用物件從客戶端例項化,並且客戶端通過使用基於租用的專用系統來管理遠端物件的生存期。

在可以接受客戶端的訪問之前,所有的遠端物件都必須用遠端處理框架註冊。物件註冊一般由宿主應用程式來完成。宿主應用程式將啟動,使用 ChannelServices 註冊一個或多個通道,使用 RemotingServices 註冊一個或多個遠端物件,然後等待被終止。請注意,已註冊的通道和物件只有在用來註冊它們的程式活動時才可以使用。如果退出了該程式,則會自動從遠端處理服務中刪除它註冊的所有通道和物件。在框架中註冊遠端物件時,需要以下四項資訊:

包含類的程式集名稱。
遠端物件的型別名稱。
客戶端定位物件時將使用的物件 URI。
伺服器啟用所需的物件模式。該模式可以是 SingleCall,也可以是 Singleton。

遠端物件可以通過下列兩種方式註冊:呼叫 RegisterWellKnownType,將上述資訊作為引數傳遞;或將上述資訊儲存在配置檔案中,然後呼叫 ConfigureRemoting 並將該配置檔案的名稱作為引數傳遞。以上兩種方法執行的功能相同,因此您可以使用它們中的任意一種來註冊遠端物件。當然,後一種方法更方便些,因為無需重新編譯宿主應用程式即可改變配置檔案的內容。以下程式碼片斷顯示瞭如何將 HelloService 類註冊為 SingleCall 遠端物件。

RemotingServices.RegisterWellKnownType(
 "server",
 "Samples.HelloServer",
 "SayHello",
  WellKnownObjectMode.SingleCall);
其中,“server”是程式集的名稱,HelloServer 是類的名稱,SayHello 是物件 URI。

註冊了遠端物件後,框架將為該物件建立一個物件引用,然後從程式集中提取與該物件相關的必要後設資料。隨後,這一資訊將與 URI 和程式集名稱一起儲存在物件引用中(該物件引用將被寫入一個用於跟蹤已註冊遠端物件的遠端處理框架表中)。請注意,除了在客戶端試圖呼叫物件上的某個方法或從客戶端啟用物件時以外,註冊程式不會例項化遠端物件自身。

現在,任何知道該物件 URI 的客戶端都可以使用 ChannelServices 註冊通道,並呼叫 new、GetObject 或 CreateInstance 啟用物件,從而獲得該物件的一個代理。以下程式碼片斷顯示了該操作的示例:

    ChannelServices.RegisterChannel(new TCPChannel);
   HelloServer bj = (HelloServer)Activator.GetObject(
     typeof(Samples.HelloServer), " tcp://localhost:8085/SayHello");
其中,“ tcp://localhost:8085/SayHello”表示我們希望在埠 8085 上使用 TCP 協議連線到位於 SayHello 終結點的遠端物件。在編譯該客戶端程式碼時,編譯器明顯會要求關於 HelloServer 類的型別資訊。該資訊可以通過以下方式之一來提供:

提供對 HelloService 類所在程式集的引用。

將遠端物件拆分為實現和介面類,並在編譯客戶端時引用這些介面。
使用 SOAPSUDS 工具直接從終結點提取所需的後設資料。此工具將連線至所提供的終結點,提取後設資料,然後生成可用於編譯客戶端的程式集或原始碼。

GetObject 或 new 可用於伺服器啟用物件。請注意,使用這兩個呼叫時不會例項化物件,實際上不會生成任何網路呼叫。框架從後設資料獲得了建立代理所需的足夠資訊,但並未連線到遠端物件上。只有在客戶端呼叫代理上的某個方法時才會建立網路連線。當呼叫抵達伺服器時,框架將從訊息中提取 URI,檢查遠端處理框架表以便定位與 URI 匹配的物件引用,然後在必要時將物件例項化,並將方法呼叫轉發至物件。如果將物件註冊為 SingleCall,則完成方法呼叫後該物件會取消。每次呼叫一個方法時,都會建立一個新的例項。GetObject 和 new 之間的唯一差別在於,前者允許指定 URL 作為引數,而後者從配置中獲得 URL。

CreateInstance 或 new 可用於客戶端啟用物件。兩者都允許使用帶引數的建構函式來例項化物件。客戶端啟用物件的生存期由遠端處理框架提供的租用服務控制。物件租用的內容在下一節中說明。


--------------------------------------------------------------------------------

物件的租用生存期

每個應用程式域都包含一個用於管理其租用情況的租用管理器。所有的租用都會被定期檢查,以確定租用是否已過期。如果租用過期,則會呼叫該租用的一個或多個發起者,使它們有機會更新租用。如果所有的發起者都不準備更新租用,則租用管理器會刪除該租用並將該物件作為垃圾回收。租用管理器按照剩餘租用時間的順序維護租用列表。剩餘時間最短的租用排在列表的頂端。

租用可以實現 ILease 介面並儲存一個屬性集合,用於確定更新的策略和方法。您也可以使用呼叫來更新租用。每次呼叫遠端物件上的方法時,租用時間都會設定為目前 LeaseTime 最大值加上 RenewOnCallTime。LeaseTime 即將過期時,發起者會被要求更新租用。因為我們有時會遇上網路不穩定,所以可能會找不到租用發起者。為了確保不在伺服器上留下無效物件,每個租用都帶有一個 SponsorshipTimeout。該值指定了租用終止之前等待租用發起者回復的時間長度。如果 SponsershipTimeout 為零,CurrentLeaseTime 會被用於確定租用的過期時間。如果 CurrentLeaseTime 的值為零,則租用不會過期。配置或 API 可用於替代 InitialLeaseTime、SponsorshipTimeout 和 RenewOnCallTime 的預設值。

租用管理器維護著一個按發起時間從大到小儲存的發起者列表(它們實現 ISponsor 介面)。需要呼叫發起者以更新租用時間時,租用管理器會從列表的頂部開始向一個或多個發起者要求更新租用時間。列表頂部的發起者表示其以前請求的租用更新時間最長。如果發起者沒有在 SponsorshipTimeOut 時間段內響應,則它會被從列表中刪除。通過呼叫 GetLifetimeService 並將物件租用作為引數,即可以獲得該物件租用。該呼叫是 RemotingServices 類的一個靜態方法。如果物件在應用程式域內部,則該呼叫的引數是物件的本地引用,且返回的租用也是該租用的本地引用。如果物件是遠端的,則代理會作為一個引數傳遞,且返回給呼叫方的是租用的透明代理。

物件能夠提供自己的租用並控制自己的生存期。它們通過替代 MarshalByRefObject 上的 InitializeLifetimeService 方法來完成該操作,如下所示:

public class Foo : MarshalByRefObject {
 public override Object InitializeLifetimeService()
 {
  ILease lease = (ILease)base.InitializeLifetimeService();
  if (lease.CurrentState == LeaseState.Initial) {
   lease.InitialLeaseTime = TimeSpan.FromMinutes(1);
   lease.SponsorshipTimeout = TimeSpan.FromMinutes(2);
   lease.RenewOnCallTime = TimeSpan.FromSeconds(2);
  }
  return lease;
 }
}
只有當租用處於初始狀態時,才可以更改租用屬性。InitializeLifetimeService 的實現通常呼叫基類的相應方法,來檢索遠端物件的現有租用。如果在此之前從未對該物件封送過,則返回的租用會處於其初始狀態且可以設定租用屬性。一旦封送了物件,則租用會從初始狀態變為啟用狀態,並忽略任何初始化租用屬性的嘗試(但有一種情況例外)。啟用遠端物件時將呼叫 InitializeLifetimeService。通過啟用呼叫可以提供一個租用發起者的列表,而且當租用處於啟用狀態時,可以隨時將其他發起者新增到列表中。

可以下列方式延長租用時間:

客戶端可以呼叫 Lease 類上的 Renew 方法。

租用可以向某個發起者請求 Renewal。

當客戶端呼叫物件上的某個方法時,RenewOnCall 值會自動更新租用。

一旦租用過期,其內部狀態會由 Active 變為 Expired,且不再對發起者進行任何呼叫,物件也會被作為垃圾回收。一般情況下,如果發起者分散在 Web 上或位於某個防火牆的後面,遠端物件回叫發起者時會遇到困難。因此,發起者不必與客戶端處於同一位置,只要遠端物件能夠訪問得到,它可以為網路上的任意位置。

通過租用來管理遠端物件的生存期可以作為引用計數的一種替代方法,因為當網路連線的效能不可靠時,引用計數會顯得複雜和低效。儘管有人會堅持認為遠端物件的生存期比所需的時間要長,但與引用計數和連線客戶相比,租用降低了網路的繁忙程度,將會成為一種非常受歡迎的解決方案。


--------------------------------------------------------------------------------

總結

要提供完美的、能夠滿足大多數商務應用需求的遠端處理框架,即使能夠做到,也必然會非常困難。Microsoft 提供了能夠根據需要進行擴充套件和自定義的框架,在正確的方向上邁出了關鍵的一步。


--------------------------------------------------------------------------------

附錄 A:使用 TCP 通道進行遠端處理的示例

此附錄顯示瞭如何編寫簡單的“Hello World”遠端應用程式。客戶端將一個字串傳遞到遠端物件上,該遠端物件將單詞“Hi There”附加到字串上,並將結果返回到客戶端。

將此檔案儲存為 server.cs。此處為伺服器的程式碼:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.TCP;

namespace RemotingSamples {
 public class HelloServer : IHello {

  public static int Main(string [] args) {

   TCPChannel chan = new TCPChannel(8085);
    ChannelServices.RegisterChannel(chan);
    RemotingServices.RegisterWellKnownType(
    "server", "RemotingSamples.HelloServer", "SayHello", WellKnownObjectMode.SingleCall);
   System.Console.WriteLine("請按 鍵退出...");
   System.Console.ReadLine();
   return 0;
  }

  public HelloServer()
  {
   Console.WriteLine("HelloServer 已啟用");
  }

  ~HelloServer()
  {
   Console.WriteLine("物件已清除");
  }

  public ForwardMe HelloMethod(ForwardMe obj)
  {
   Console.WriteLine("Hello.HelloMethod : {0}", name);
   return "Hi there " + name;
  }
 }
}
將此程式碼儲存為 client.cs:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.TCP;

namespace RemotingSamples {
 public class Client
 {
  public static int Main(string [] args)
  {
   TCPChannel chan = new TCPChannel();
    ChannelServices.RegisterChannel(chan);
   ForwardMe param = new ForwardMe();
   HelloServer bj = (HelloServer)Activator.GetObject(
     typeof(RemotingSamples.HelloServer), " tcp://localhost:8085/SayHello");
   if (obj == null) System.Console.WriteLine("無法定位伺服器");
   else {
    Console.WriteLine("值為 " + param.getValue());
    ForwardMe after = obj.HelloMethod(param);
     Console.WriteLine("呼叫後的值為 " + after.getValue());
   }
   return 0;
  }
 }
}
下面是 makefile:

all: server.exe client.exe share.dll

share.dll: share.cs
  csc /debug+ /target:library /out:share.dll share.cs

server.exe: server.cs
  csc /debug+ /r:share.dll /r:System.Runtime.Remoting.dll server.cs

client.exe: client.cs server.exe
  csc /debug+ /r:share.dll /r:server.exe /r:System.Runtime.Remoting.dll client.cs

clean:
  @del server.exe client.exe *.pdb *~ *.*~

--------------------------------------------------------------------------------

附錄 B:使用 HTTP 通道進行遠端處理的示例

將此檔案儲存為 server.cs。此處為伺服器的程式碼:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.HTTP;

namespace RemotingSamples {
 public class HelloServer : IHello {

  public static int Main(string [] args) {

   HTTPChannel chan = new HTTPChannel(8085);
    ChannelServices.RegisterChannel(chan);
    RemotingServices.RegisterWellKnownType(
    "server", "RemotingSamples.HelloServer", "SayHello", WellKnownObjectMode.SingleCall);
   System.Console.WriteLine("請按 鍵退出...");
   System.Console.ReadLine();
   return 0;
  }

  public HelloServer()
  {
   Console.WriteLine("HelloServer 已啟用");
  }

  ~HelloServer()
  {
   Console.WriteLine("物件已清除");
  }

  public ForwardMe HelloMethod(ForwardMe obj)
  {
   Console.WriteLine("Hello.HelloMethod : {0}", name);
   return "Hi there " + name;
  }
 }
}
將此程式碼儲存為 client.cs:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.HTTP;

namespace RemotingSamples {
 public class Client
 {
  public static int Main(string [] args)
  {
   HTTPChannel chan = new HTTPChannel();
    ChannelServices.RegisterChannel(chan);
   ForwardMe param = new ForwardMe();
   HelloServer bj = (HelloServer)Activator.GetObject(
     typeof(RemotingSamples.HelloServer), " http://localhost:8085/SayHello");
   if (obj == null) System.Console.WriteLine("無法定位伺服器");
   else {
    Console.WriteLine("值為 " + param.getValue());
    ForwardMe after = obj.HelloMethod(param);
     Console.WriteLine("呼叫後的值為 " + after.getValue());
   }
   return 0;
  }
 }
}

下面是 makefile:

all: server.exe client.exe share.dll

share.dll: share.cs
  csc /debug+ /target:library /out:share.dll share.cs

server.exe: server.cs
  csc /debug+ /r:share.dll /r:System.Runtime.Remoting.dll server.cs

client.exe: client.cs server.exe
  csc /debug+ /r:share.dll /r:server.exe /r:System.Runtime.Remoting.dll client.cs

clean:
  @del server.exe client.exe *.pdb *~ *.*~

=======================================================================================

NET Web服務和Remoting:哪個更好?

周靖 譯
Wednesday, June 11 2003 3:44 PM
  微軟一直在宣揚用Web服務來構建應用程式的好處,而且它的.NET框架確實簡化了Web服務的使用。但是,雖然Web服務確實好用,但最適合使用它的場合是:位於防火牆外部的客戶端通過因特網呼叫你的伺服器上的元件。

假如客戶端和元件都在防火牆內部,Web服務也許能很好地工作,但由於所有資料都要通過一個Web伺服器,所以有可能影響效能。為加快速度,微軟提供了一種名為Remoting的二進位制機制。


下面來看看Remoting具體如何工作,本文將用一些示範程式碼展示如何在一個Web服務中設定它。

.NET Remoting

雖然Web服務總體上說是從公司外部的客戶端訪問元件的最佳方式,但公司內部的元件又如何呢?許多公司的Web服務是在內部使用的。使用Web服務本身並沒有錯,只是它無法提供最佳的效能。如果元件是用.NET建立的,而且客戶端應用程式也是.NET,就可將元件放到共享伺服器上,並通過Remoting來訪問它們。

Remoting是用於取代DCOM的一種.NET技術,它採用二進位制格式在客戶端應用程式和元件之間通訊。這樣一來,遠端元件的速度快於Web服務。但是,建立遠端元件的難度較大,因為你必須在自己的元件中新增附加的程式碼。這些程式碼雖然並不比Web服務的程式碼複雜多少,但你不能直接例項化一個遠端元件。相反,必須建立一個主機應用程式,由它例項化元件並偵聽請求。好訊息是,這個主機可以是一個Windows服務、Windows應用程式、控制檯應用程式或者能夠執行並保持物件開啟狀態的其他任何東西。

你不僅要建立主機應用程式,還必須做出有關遠端物件的幾項決策,比如要使用哪個通道(channel)。.NET支援HTTP和TCP通道。HTTP通道實際使用SOAP協議將訊息傳輸給遠端物件,或者從遠端物件傳回訊息。這意味著所有訊息都要序列化成XML格式。TCP通道則使用一個二進位制的流來傳輸訊息。

接著,必須在兩種啟用模式中選擇其一:Singleton和SingleCall。其中,Singleton型別在任何時候都只有物件的一個例項。所有客戶端請求都由那個例項提供服務。這樣就允許你在請求之間“共享”資料,或者在不同請求之間維持狀態。另一方面,SingleCall型別會為每個客戶端請求都建立一個新的物件例項。SingleCall物件更像是Web服務,因為它們是無狀態的,而且會針對每個請求建立和銷燬。

遠端元件在改變通道時,不需要重新編譯,這一點要歸功於.NET巧妙的設計。你可將通道檔案放到一個配置檔案中,並從TCP變成HTTP,或從HTTP變成TCP,應用程式無需重新編譯。類似地,你可為客戶端更改配置檔案,與主機使用的通道相匹配。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-629674/,如需轉載,請註明出處,否則將追究法律責任。

相關文章