明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

Microsoft .NET Remoting:技術概述

[摘要]Microsoft .NET Remoting:技術概述Piet Obermeyer 和 Jonathan HawkinsMicrosoft Corporation摘要:本文提供了 Microsof...
Microsoft .NET Remoting:技術概述
Piet Obermeyer 和 Jonathan Hawkins
Microsoft Corporation

摘要:本文提供了 Microsoft .NET Remoting 框架的技術概述,其中包括了使用 TCP 通道或 HTTP 通道的示例。

目錄
簡介
遠程對象
代理對象
通道
激活
對象的租用生存期
總結
附錄 A:使用 TCP 通道進行遠程處理的示例
附錄 B:使用 HTTP 通道進行遠程處理的示例

簡介
Microsoft® .NET Remoting 提供了一種允許對象通過應用程序域與另一對象進行交互的框架。這種框架提供了多種服務,包括激活和生存期支持,以及負責與遠程應用程序進行消息傳輸的通訊通道。格式化程序用于在消息通過通道傳輸之前,對其進行編碼和解碼。應用程序可以在注重性能的場合使用二進制編碼,在需要與其他遠程處理框架進行交互的場合使用 XML 編碼。在從一個應用程序域向另一個應用程序域傳輸消息時,所有的 XML 編碼都使用 SOAP 協(xié)議。出于安全性方面的考慮,遠程處理提供了大量掛鉤,使得在消息流通過通道進行傳輸之前,安全接收器能夠訪問消息和序列化流。

通常,如果沒有底層框架的支持,管理遠程對象的生存期會非常麻煩。.NET Remoting 提供了許多可供選擇的生存期模型,這些模型分為兩個類別:

客戶端激活對象


服務器激活對象
客戶端激活對象受基于租用的生存期管理器的控制,這種管理器確保了租用期滿時對象可被回收。而對于服務器激活對象,開發(fā)人員則可以選擇“單一調用”模式或“單一元素”模式。

遠程對象
任何遠程處理框架的主要目的之一就是要提供必要的基礎結構,以便隱藏遠程對象調用方法和返回結果的復雜性。任何位于調用方應用程序域之外的對象,即使在同一臺計算機上執(zhí)行,也會被認為是遠程對象。在應用程序域內部,原始數據類型按數值傳遞,而所有的對象按引用傳遞。因為本地對象引用僅在創(chuàng)建對象的應用程序域內有效,所以它們不能以這種方式傳遞到遠程方法調用或從遠程方法調用返回。所有必須跨越應用程序域的本地對象都必須按數值來傳遞,并且應該用 [serializable] 自定義屬性作標記,否則它們必須實現 ISerializable 接口。對象作為參數傳遞時,框架將該對象序列化并傳輸到目標應用程序域,對象將在該目標應用程序域中被重新構造。無法序列化的本地對象將不能傳遞到其他應用程序域中,因而也不能遠程處理。

通過從 MarshalByRefObject 導出對象,您可以使任一對象變?yōu)檫h程對象。當某個客戶端激活一個遠程對象時,它將接收到該遠程對象的代理。對該代理的所有操作都被適當地重新定向,使遠程處理基礎結構能夠正確截取和轉發(fā)調用。盡管這種重新定向對性能有一些影響,但 JIT 編譯器和執(zhí)行引擎 (EE) 已經優(yōu)化,可以在代理和遠程對象駐留在同一個應用程序域中時,防止不必要的性能損失。如果代理和遠程對象不在同一個應用程序域中,則堆棧中的所有方法調用參數會被轉換為消息并被傳輸到遠程應用程序域,這些消息將在該遠程應用程序域中被轉換為原來的堆棧幀,同時該方法調用也會被調用。從方法調用中返回結果時也使用同一過程。

代理對象
代理對象是在客戶端激活遠程對象時創(chuàng)建的。作為遠程對象的代表,代理對象確保對代理進行的所有調用都能夠轉發(fā)到正確的遠程對象實例。為了準確理解代理對象的工作方式,我們需要更深入地研究它們。當某個客戶端激活一個遠程對象時,框架將創(chuàng)建 TransparentProxy 類的一個本地實例(該類中包含所有類的列表與遠程對象的接口方法)。因為 TransparentProxy 類在創(chuàng)建時用 CLR 注冊,所以代理上的所有方法調用都被運行時截取。這時系統(tǒng)將檢查調用,以確定其是否為遠程對象的有效調用,以及遠程對象的實例是否與代理位于同一應用程序域中。如果對象在同一個應用程序域中,則簡單方法調用將被路由到實際對象;如果對象位于不同的應用程序域中,將通過調用堆棧中的調用參數的 Invoke 方法將其打包到 IMessage 對象并轉發(fā)到 RealProxy 類中。此類(或其內部實現)負責向遠程對象轉發(fā)消息。TransparentProxy 類和 RealProxy 類都是在遠程對象被激活后在后臺創(chuàng)建的,但只有 TransparentProxy 返回到客戶端。

要更好地理解這些代理對象,我們需要簡要介紹一下 ObjRef。激活一節(jié)中有關于 ObjRef 的詳細說明。以下方案簡要說明了 ObjRef 與這兩個代理類的關聯方式。但請注意,這只是關于該進程的一個極其概括的說明;根據對象是客戶端激活對象還是服務器激活對象,以及它們是單一元素對象還是單一調用對象,該進程會有所不同。

遠程對象注冊在遠程計算機的應用程序域中。遠程對象被封送以生成 ObjRef。ObjRef 包含了從網絡上的任意位置定位和訪問遠程對象所需的所有信息,包括:類的增強名稱、類的層次結構(其父類)、類實現的所有接口的名稱、對象 URI 和所有已注冊的可用通道的詳細信息。在接收到對某個遠程對象的請求時,遠程處理框架使用對象 URI 來檢索為該對象創(chuàng)建的 ObjRef 實例。


客戶端通過調用 new 或某個 Activator 函數(例如 CreateInstance)來激活遠程對象。對于服務器激活對象,遠程對象的 TransparentProxy 將在客戶端應用程序域中生成并返回到客戶端,這時不執(zhí)行任何遠程調用。只有在客戶端調用遠程對象的某個方法時,該遠程對象才會被激活。此方案明顯不適合客戶端激活對象,因為客戶端希望框架只在得到請求時才激活對象。當客戶端調用某個激活方法時,客戶端上會創(chuàng)建一個激活代理,并且將使用 URL 和對象 URI 作為終結點在服務器的遠程激活器上初始化一個遠程調用。遠程激活器激活該對象,然后 ObjRef 流向客戶端,并被取消封送以生成一個返回給客戶端的 TransparentProxy。


取消封送的過程中會分析 ObjRef 以提取遠程對象的方法信息,同時還會創(chuàng)建 TransparentProxy 和 RealProxy 對象。在用 CLR 注冊 TransparentProxy 之前,分析后的 ObjRef 內容會被添加到 TransparentProxy 的內部表中。
TransparentProxy 是一種無法替代和擴展的內部類,而 RealProxy 和 ObjRef 類則屬于公共類,可以在必要時進行擴展和自定義。因為 RealProxy 類能夠處理遠程對象的所有函數調用,所以它是執(zhí)行負載平衡等操作的理想方法。調用 Invoke 時,從 RealProxy 導出的類可以獲得網絡中服務器的負載信息,并將該調用路由到適當的服務器。簡單地為所需的 ObjectURI 從通道請求一個 MessageSink,并調用 SyncProcessMessage 或 AsyncProcessMessage 以將該調用轉發(fā)至所需的遠程對象。當調用返回時,通過調用 RemotingServices 類的 PropagateMessageToProxy 將返回參數推回到堆棧中。

下面的代碼片斷顯示了如何使用導出的 RealProxy 類。

MyRealProxy proxy = new MyRealProxy(typeof(Foo));
Foo obj = (Foo)proxy.GetTransparentProxy();
int result = obj.CallSomeMethod();

上例中獲取的 TransparentProxy 可以被轉發(fā)到另一個應用程序域中。當第二個客戶端試圖調用代理上的某個方法時,遠程處理框架會嘗試創(chuàng)建 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;}
}
}

通道
通道用于在遠程對象之間傳輸消息。當客戶端調用某個遠程對象上的方法時,與該調用相關的參數以及其他詳細信息會通過通道傳輸到遠程對象。調用的任何結果都會以同樣的方式返回給客戶端。客戶端可以選擇“服務器”中注冊的任一通道,以實現與遠程對象之間的通訊,因此開發(fā)人員可以自由選擇最適合需要的通道。當然,也可以自定義任何現有的通道或創(chuàng)建使用其他通訊協(xié)議的新通道。通道選擇遵循以下規(guī)則:

在能夠調用遠程對象之前,遠程處理框架必須至少注冊一個通道。通道注冊必須在對象注冊之前進行。


通道按應用程序域注冊。一個進程中可以有多個應用程序域。當進程結束時,該進程注冊的所有通道將被自動清除。


多次注冊偵聽同一端口的通道是非法的。即使通道按應用程序域注冊,同一計算機上的不同應用程序域也不能注冊偵聽同一端口的通道。


客戶端可以使用任何已注冊的通道與遠程對象通訊。當客戶端試圖連接至某個遠程對象時,遠程處理框架會確保該對象連接至正確的通道?蛻舳素撠熢趪L試與遠程對象通訊之前調用 ChannelService 類的 RegisterChannel。
所有的通道都由 IChannel 導出,并根據通道的用途實現 IChannelReceiver 或 IchannelSender。大多數通道既實現了接收器接口,又實現了發(fā)送器接口,使它們可以在兩個方向上通訊。當客戶端調用代理上的某個方法時,遠程處理框架會截取該調用并將其轉為要發(fā)送到 RealProxy 類(或一個實現 RealProxy 類的實例)的消息。RealProxy 將消息轉發(fā)到消息接收器以進行處理。消息接收器負責與遠程對象注冊的通道之間建立連接,并通過通道(在不同的應用程序域)將消息從調度位置傳輸到遠程對象本身。激活了一個遠程對象后,客戶端會通過調用選定通道上的 CreateMessageSink 來選擇通道,并從其上檢索能夠與遠程對象通訊的消息接收器。

遠程處理框架的一個容易混淆的方面是遠程對象和通道之間的關系。例如,如果 SingleCall 遠程對象只在被調用時才激活,那么該對象如何偵聽要連接的客戶端?

部分答案在于這樣一個事實:遠程對象并不擁有自己的通道,而是共享通道。作為遠程對象宿主的服務器應用程序必須注冊要通過遠程處理框架公開的對象以及所需的通道。注冊后的通道會自動開始在指定的端口偵聽客戶請求。注冊遠程對象后,會為該對象創(chuàng)建一個 ObjRef 并將其存儲在表中。當通道上傳來一個請求時,遠程處理框架會檢查該消息以確定目標對象,同時檢查對象引用表以定位表中的引用。如果找到了對象引用,將從表中檢索框架目標對象或在必要時將其激活,然后框架將調用轉發(fā)至該對象。對于同步調用,在消息調用期間會一直維持來自客戶端的連接。因為每個客戶端連接都在自己的線程上處理,所以一個通道可以同時服務于多個客戶端。

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

HTTP 通道
HTTP 通道使用 SOAP 協(xié)議與遠程對象傳輸消息。所有的消息流過 SOAP 格式化程序時都被轉換為 XML 格式且被序列化,所需的 SOAP 頭也會被添加到該流中。您也可以指定能夠生成二進制數據流的二進制格式化程序。然后,數據流會使用 HTTP 協(xié)議傳輸到目標 URI。

TCP 通道
TCP 通道使用二進制格式化程序將所有的消息序列化為二進制流,并使用 TCP 協(xié)議將其傳輸到目標 URI。

激活
遠程處理框架支持遠程對象的服務器激活和客戶端激活。不需要遠程對象在方法調用之間維護任何狀態(tài)時,一般使用服務器激活。服務器激活也適用于多個客戶端調用方法位于同一對象實例上、且對象在函數調用之間維持狀態(tài)的情況。另一方面,客戶端激活對象從客戶端實例化,并且客戶端通過使用基于租用的專用系統(tǒng)來管理遠程對象的生存期。

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

包含類的程序集名稱。


遠程對象的類型名稱。


客戶端定位對象時將使用的對象 URI。


服務器激活所需的對象模式。該模式可以是 SingleCall,也可以是 Singleton。
遠程對象可以通過下列兩種方式注冊:調用 RegisterWellKnownType,將上述信息作為參數傳遞;或將上述信息存儲在配置文件中,然后調用 ConfigureRemoting 并將該配置文件的名稱作為參數傳遞。以上兩種方法執(zhí)行的功能相同,因此您可以使用它們中的任意一種來注冊遠程對象。當然,后一種方法更方便些,因為無需重新編譯宿主應用程序即可改變配置文件的內容。以下代碼片斷顯示了如何將 HelloService 類注冊為 SingleCall 遠程對象。

RemotingServices.RegisterWellKnownType(
"server",
"Samples.HelloServer",
"SayHello",
WellKnownObjectMode.SingleCall);

其中,“server”是程序集的名稱,HelloServer 是類的名稱,SayHello 是對象 URI。

注冊了遠程對象后,框架將為該對象創(chuàng)建一個對象引用,然后從程序集中提取與該對象相關的必要元數據。隨后,這一信息將與 URI 和程序集名稱一起存儲在對象引用中(該對象引用將被寫入一個用于跟蹤已注冊遠程對象的遠程處理框架表中)。請注意,除了在客戶端試圖調用對象上的某個方法或從客戶端激活對象時以外,注冊進程不會實例化遠程對象自身。

現在,任何知道該對象 URI 的客戶端都可以使用 ChannelServices 注冊通道,并調用 new、GetObject 或 CreateInstance 激活對象,從而獲得該對象的一個代理。以下代碼片斷顯示了該操作的示例:

ChannelServices.RegisterChannel(new TCPChannel);
HelloServer obj = (HelloServer)Activator.GetObject(
typeof(Samples.HelloServer), "tcp://localhost:8085/SayHello");

其中,“tcp://localhost:8085/SayHello”表示我們希望在端口 8085 上使用 TCP 協(xié)議連接到位于 SayHello 終結點的遠程對象。在編譯該客戶端代碼時,編譯器明顯會要求關于 HelloServer 類的類型信息。該信息可以通過以下方式之一來提供:

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


將遠程對象拆分為實現和接口類,并在編譯客戶端時引用這些接口。


使用 SOAPSUDS 工具直接從終結點提取所需的元數據。此工具將連接至所提供的終結點,提取元數據,然后生成可用于編譯客戶端的程序集或源代碼。
GetObject 或 new 可用于服務器激活對象。請注意,使用這兩個調用時不會實例化對象,實際上不會生成任何網絡調用?蚣軓脑獢祿@得了創(chuàng)建代理所需的足夠信息,但并未連接到遠程對象上。只有在客戶端調用代理上的某個方法時才會建立網絡連接。當調用抵達服務器時,框架將從消息中提取 URI,檢查遠程處理框架表以便定位與 URI 匹配的對象引用,然后在必要時將對象實例化,并將方法調用轉發(fā)至對象。如果將對象注冊為 SingleCall,則完成方法調用后該對象會取消。每次調用一個方法時,都會創(chuàng)建一個新的實例。GetObject 和 new 之間的唯一差別在于,前者允許指定 URL 作為參數,而后者從配置中獲得 URL。

CreateInstance 或 new 可用于客戶端激活對象。兩者都允許使用帶參數的構造函數來實例化對象?蛻舳思せ顚ο蟮纳嫫谟蛇h程處理框架提供的租用服務控制。對象租用的內容在下一節(jié)中說明。

對象的租用生存期
每個應用程序域都包含一個用于管理其租用情況的租用管理器。所有的租用都會被定期檢查,以確定租用是否已過期。如果租用過期,則會調用該租用的一個或多個發(fā)起者,使它們有機會更新租用。如果所有的發(fā)起者都不準備更新租用,則租用管理器會刪除該租用并將該對象作為垃圾回收。租用管理器按照剩余租用時間的順序維護租用列表。剩余時間最短的租用排在列表的頂端。

租用可以實現 ILease 接口并存儲一個屬性集合,用于確定更新的策略和方法。您也可以使用調用來更新租用。每次調用遠程對象上的方法時,租用時間都會設置為目前 LeaseTime 最大值加上 RenewOnCallTime。LeaseTime 即將過期時,發(fā)起者會被要求更新租用。因為我們有時會遇上網絡不穩(wěn)定,所以可能會找不到租用發(fā)起者。為了確保不在服務器上留下無效對象,每個租用都帶有一個 SponsorshipTimeout。該值指定了租用終止之前等待租用發(fā)起者回復的時間長度。如果 SponsershipTimeout 為零,CurrentLeaseTime 會被用于確定租用的過期時間。如果 CurrentLeaseTime 的值為零,則租用不會過期。配置或 API 可用于替代 InitialLeaseTime、SponsorshipTimeout 和 RenewOnCallTime 的默認值。

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

對象能夠提供自己的租用并控制自己的生存期。它們通過替代 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;
}
}

只有當租用處于初始狀態(tài)時,才可以更改租用屬性。InitializeLifetimeService 的實現通常調用基類的相應方法,來檢索遠程對象的現有租用。如果在此之前從未對該對象封送過,則返回的租用會處于其初始狀態(tài)且可以設置租用屬性。一旦封送了對象,則租用會從初始狀態(tài)變?yōu)榧せ顮顟B(tài),并忽略任何初始化租用屬性的嘗試(但有一種情況例外)。激活遠程對象時將調用 InitializeLifetimeService。通過激活調用可以提供一個租用發(fā)起者的列表,而且當租用處于激活狀態(tài)時,可以隨時將其他發(fā)起者添加到列表中。

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

客戶端可以調用 Lease 類上的 Renew 方法。


租用可以向某個發(fā)起者請求 Renewal。


當客戶端調用對象上的某個方法時,RenewOnCall 值會自動更新租用。
一旦租用過期,其內部狀態(tài)會由 Active 變?yōu)?Expired,且不再對發(fā)起者進行任何調用,對象也會被作為垃圾回收。一般情況下,如果發(fā)起者分散在 Web 上或位于某個防火墻的后面,遠程對象回叫發(fā)起者時會遇到困難。因此,發(fā)起者不必與客戶端處于同一位置,只要遠程對象能夠訪問得到,它可以為網絡上的任意位置。

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

總結
要提供完美的、能夠滿足大多數商務應用需求的遠程處理框架,即使能夠做到,也必然會非常困難。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("請按 <enter> 鍵退出...");
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 obj = (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("請按 <enter> 鍵退出...");
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 obj = (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 *~ *.*~









--------------------------------------------------------------------------------
請以 IE4.0 以上版本 800 * 600 瀏覽本站
&copy;2001 Microsoft Corporation 版權所有。保留所有權利。使用規(guī)定。