天天看點

.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2

.Net Remoting(基本操作) - Part.2

Remoting 構架

接下來我們考慮通常的情況,也就是 客戶程式 與 宿主程式 位于不同的程序中的情況。

NOTE:

因為我是在我本地機器做的測試,是以隻是位于不同程序,實際上位于不同機器中的操作是完全一樣的,僅僅是Uri不同,下面将會看到。

Remoting 是.Net Framework的一個組成部分,作為一個架構(Framework),兩個必備的特性是

基本實作

可擴充(可定制)

。基本實作的意思是說:對于Remoting機制的各個組成部分,.Net 已經提供了一至兩個基本實作,可以直接使用;而可擴充的意思是說:對于每個組成部分,都可以由Framework的使用者自行定制。Remoting 的構架也是如此,它的幾乎每一個部分都是可以由程式員提供實作的,但是.Net也提供了一套預設實作,通常情況下是沒有必要自行定制的。本章主要講述Remoting的各個組成部分。

1.用戶端(客戶應用程式)

用戶端的處理包含三個基本的組成部分,代理(Proxy)、格式器(Formatter) 和 通道(Channel)。

用戶端總是通過一個代理來和服務端對象進行互動。用戶端向代理請求屬性或者方法調用,然後代理将請求發送給服務端的對象。每一個代理綁定一個遠端對象,多個代理也可以綁定同一個對象(Singleton方式,後面會介紹);用戶端的多個對象也可以使用同一個代理。代理分為兩部分,一個名為透明代理(Transparent Proxy),一個名為真實代理(Real Proxy)。透明代理提供了和服務對象完全一緻的公共接口,當客戶進行方法調用時,透明代理将棧幀(Stack Frame,在棧中為參數、傳回位址和局部變量保留的一塊記憶體區,必要時在過程調用中使用)轉換為消息(Message),然後将消息發送給真實代理。這個消息對象包含了調用的對象的方法資訊,包括方法簽名、參數等,同時還包括用戶端的位置(注意這裡,方法回調(Callback)時會再提到)。真實代理知道如何連接配接遠端對象并将消息發送給它。

真實代理收到消息後,請求Formatter 對象對其進行序列化,同時将客戶程式中斷(block)。.Net 内置了兩種序列化格式,一種是二進制Binary,一種是SOAP。Formatter将消息進行序列化之後,然後将其發送到通道中,由通道将消息發送到遠端對象。當請求傳回時,Formatter将傳回的消息反序列化,然後再送出給代理,代理将傳回值放到發送請求的客戶對象的調用堆棧上,随後将控制傳回給客戶調用程式(解除中斷)。這樣就給了客戶對象一個錯覺:代理即為遠端對象。

2.服務端(宿主應用程式)

服務端主要由 通道(Channel)、格式器(Formatter)、Stack Builder組成。

在服務端,宿主程式保持着為Remoting所打開的端口的監聽,一旦通道收到消息,它便将消息發送給Formatter,Formatter将消息進行反序列化,然後将消息發送給Stack Builder,Stack Builder讀取消息,然後依據消息建立對象(可選),調用方法。方法傳回時,Stack Builder将傳回值封裝為消息,然後再送出給Formatter,Formatter進行格式化之後,發送到通道傳遞消息。

3.Remoting對象的三種激活方式

上一章 .Net Remoting - Part.1 中,我們提到了傳值封送和傳引用封送,并各給出了一張示意圖,實際上,傳引用封送還分為了三種不同的方式,下面來一一來介紹。對于傳引用封送,記住各種方式的共同點:

服務對象建立且一直保持在宿主程式中

。我知道Remoting的概念多得已經讓你厭煩,而且在不結合例子的情況下很難了解,是以這小節我們僅歸納它的特點,到後面例子中,我們再詳細看。

3.1客戶激活(Client activated )

客戶激活方式我們實際上已經了解過了,就是在Part.1中我們在單一程序中跨應用程式域傳引用封送時的情況,我們再來回顧一下這張圖:

.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2

結合這幅圖,我們可以看出對于每個客戶,建立了其專屬的遠端對象為其服務(由Part.1的代碼可以看出,對象的狀态在兩次方法調用中也是維持着的)。除此以外,一個代理可以為多個客戶對象服務。

客戶激活模式的缺點就是 如果用戶端過多時,或者服務對象為“大對象”時,伺服器端的壓力過大。另外,客戶程式可能隻需要調用服務對象的一個方法,但是卻持有服務對象過長時間,這樣浪費了伺服器的資源。

3.2 服務激活 Singleton(Server activated Singleton)

這個模式的最大特色就是所有的客戶共享同一個對象。服務端隻在對象第一次被調用時建立服務對象,對于後繼的通路使用同一個對象提供服務。如下圖所示:

.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2

因為Sinlgton對象是在第一次通路(比如方法調用)時由.Net自動建立的,後繼的通路不能重新建立對象,是以它不提供有參數的構造函數。另外,由于Singleton對象的狀态由其它對象所共享,是以使用Singleton對象應該考慮線程同步 的問題。

3.3 服務激活 Single Call(Server activated Single Call)

Single Call方式是對每一次請求(比如方法調用)建立一個對象,而在每次方法傳回之後銷毀對象。由此可見Single Call 方式的最大特點就是

不儲存狀态

。使用Single Call的好處就是不會過久地占用資源,因為方法傳回後對資源的占用就随對象被銷毀而釋放了。最後,Single Call 方式也不允許使用由參數的構造函數。

Remoting程式的基本操作

在這一章我們綜合前面的知識,通過編碼的方式一步步實作一個Remoting的範例程式,以此來熟悉Remoting的一些基本操作和步驟,并對前面的知識加深一下了解。

1.服務程式集

我們首先建立服務程式集,它即為向客戶程式提供服務的遠端對象的實作代碼。先建立一個類庫項目ServerAssembly,然後建立類型ServerAssembly.DemoClass(為Part.1中的ClassLib.DemoClass添加了幾個方法)。我們讓它繼承自MarshalByRefObject,使用更為常用的傳引用封送形式:

public class DemoClass:MarshalByRefObject {

    private int count = 0;

    public DemoClass() {

        Console.WriteLine("\n======= DomoClass Constructor =======");

    }

    public void ShowCount(string name) {

        count++;

        Console.WriteLine("{0},the count is {1}.", name, count);

    // 列印對象所在的應用程式域

    public void ShowAppDomain() {

        AppDomain currentDomain = AppDomain.CurrentDomain;

        Console.WriteLine(currentDomain.FriendlyName);

    public int GetCount() {

        return count;

}

建立這幾個方法的作用如下:

  • DemoClass()構造函數用于追蹤遠端對象建立的時機。
  • ShowCount()方法用于測試向遠端對象傳遞參數,以及對象狀态的儲存。
  • ShowAppDomain()方法用于驗證對象建立的位置(是否真的位于遠端)。
  • GetCount()方法用于測試擷取遠端對象的傳回值。

2.宿主應用程式

接下來我們新建立一個空解決方案ServerSide,在其下添加一個新的控制台項目ServerConsole,然後再将上面建立的項目ServerAssembly添加進來。除此以外,還需要添加System.Runtime.Remoting的引用,它一般位于C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Runtime.Remoting.dll(修改系統目錄)。

2.1 注冊通道

實作宿主應用程式的第一步就是注冊通道。通道是實作了System.Runtime.Remoting.Channels.IChannel的類。通道分為兩種,一種是發送請求的通道,比如說客戶應用程式使用的通道,這種類型的通道還需要實作 System.Runtime.Remoting.Channels.IChannelSender 接口;一種是接收請求的通道,比如說宿主應用程式使用的通道,這種類型的通道還需實作System.Runtime.Remoting.Channels.IChannelReceiver接口。

通常我們不需要實作自己的通道,.Net 提供了三個内置的通道,分别是 System.Runtime.Remoting.Channels.Http.HttpChannel、System.Runtime.Remoting.Channels.Tcp.TcpChannel 以及 System.Runtime.Remoting.Channels.Ipc.IpcChannel。由于 IpcChannel 不能跨機器(隻能跨程序),是以我們僅使用最為常用的 HttpChannel和TcpChannel為例作為說明。它們均實作了 System.Runtime.Remoting.Channels 命名空間下的 IChannel、IChannelSender、IChannelReceiver接口,是以它們既可以用于發送請求,也可以用于接收請求。

接下來需要對通道進行注冊,然後對這個通道進行監聽。對于同一個應用程式域,同一類型(實際上是同一名稱,因為同一類型的通道預設名稱相同)的通道隻能注冊一次。對同一機器來說,同一端口也隻能使用一次。同一應用程式域可以注冊多個不同類型的通道。注冊的方式是調用ChannelServices類型的靜态方法RegisterChannel():

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

using System.Runtime.Remoting.Channels.Http;

namespace ServerConsole {

    class Program {

        static void Main(string[] args) {

            RegisterChannel();      // 注冊2個通道

            RemotingConfiguration.ApplicationName = "SimpleRemote";

            Console.WriteLine("服務開啟,按任意鍵退出...");

            Console.ReadKey();

        }

        private static void RegisterChannel() {

            // 建立通道執行個體

            // IChannel tcpChnl = new TcpChannel(8501);

            IChannelReceiver tcpChnl = new TcpChannel(8501);

            // 注冊tcp通道

            ChannelServices.RegisterChannel(tcpChnl, false);

            // 注冊http通道

            IChannel httpChnl = new HttpChannel(8502);

            ChannelServices.RegisterChannel(httpChnl, false);

上面的程式便成功注冊了兩個端口用于Remoting程式的監聽。運作程式,然後在Windows的指令提示符中輸入 netstat -a,檢視端口狀況,可以看到這兩個端口位于監聽狀态(最後兩行):

.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2

當通道從端口監聽到新請求時,它會從線程池中抓取一個線程執行請求,進而可以不間斷地對端口進行監聽(不會因為處理請求而中斷)。當關閉宿主程式時,.Net會自動釋放端口,以便其他程式可以使用該端口。

在上面我們已經提到消息(Message)以某種特定格式通過通道傳遞。當我們使用上面的構造函數建立通道時,消息會以通道所預設的消息格式傳遞。對于TcpChannel來說,使用二進制,也就是Binary 格式;對于HttpChannel來說,使用SOAP消息格式。我們也可以使用重載的構造函數建立通道,指定通道所采用的消息格式,以TcpChannel為例:

public TcpChannel(IDictionary properties,

    IClientChannelSinkProvider clientSinkProvider,

    IServerChannelSinkProvider serverSinkProvider);

IDictionary是key已經預先定義好了的 屬性/值 集合,屬性有通道名稱、端口号等。IClientChannelSinkProvider可以用于提供用戶端通道消息所采用的格式;IServerChannelSinkProvider 用于提供服務端通道消息所采用的格式。我們知道一個通道執行個體不會同時用于位于用戶端和服務端,是以,當建立服務端通道時,将IClientChannelSinkProvider設為null就可以了;同理,建立用戶端通道時,将IServerChannelSinkProvider設為null。現在我們将上面的例子改一下,顯示地設定通道所采用的消息格式:

class Program {

    static void Main(string[] args) {

        RegisterChannel();      // 先以第一種方式注冊2個通道

        RegisterChannel2();     // 以自定義模式注冊1個通道

        RemotingConfiguration.ApplicationName = "SimpleRemote";

        Console.WriteLine("服務開啟,可按任意鍵退出...");

        Console.ReadKey();

    // 自定義Formatter和通道名稱的注冊方式

    private static void RegisterChannel2() {

        IServerChannelSinkProvider formatter;

        formatter = new BinaryServerFormatterSinkProvider();

        IDictionary propertyDic = new Hashtable();

        propertyDic["name"] = "CustomTcp";

        propertyDic["port"] = 8503;

        IChannel tcpChnl = new TcpChannel(propertyDic, null, formatter);

        ChannelServices.RegisterChannel(tcpChnl, false);

    private static void RegisterChannel(){...} // 略

注意到上面我們通過propertyDic将通道的名稱設為了CustomTcp,而在RegisterChannel()方法中,我們沒有設定(此時,對于TcpChannel,會采用了預設名稱:tcp)。通過顯示指定通道名稱的方式,對于同一種類型的通道,我們進行了多次注冊。現在在指令提示符中輸入 netstat -a ,應該可以看到一共監聽了三個端口。

2.2 注冊對象

注冊通道之後,我們需要告訴.Net允許哪些類型可以被遠端程式通路,這一步驟稱為注冊對象。如同上面所說的,有三種伺服器端的遠端對象類型:客戶激活對象、服務激活Single Call、服務激活Singleton。

客戶激活對象的注冊方式需要使用RemotingConfiguration類型的RegisterActivatedServiceType()靜态方法:

// 注冊 客戶激活對象 Client Activated Object

private static void ClientActivated() {

    Console.WriteLine("方式: Client Activated Object");

    Type t = typeof(DemoClass);

    RemotingConfiguration.RegisterActivatedServiceType(t);

服務激活對象 可以使用RemotingConfiguration類型的 RegisterWellKnownServiceType()靜态方法:

// 注冊 服務激活對象 SingleCall

private static void ServerActivatedSingleCall() {

    Console.WriteLine("方式: Server Activated SingleCall");

    RemotingConfiguration.RegisterWellKnownServiceType(

        t, "ServerActivated", WellKnownObjectMode.SingleCall);

// 注冊 服務端激活對象 Singleton

private static void ServerActivatedSingleton() {

    Console.WriteLine("方式: Server Activated Singleton");

        t, "ServerActivated", WellKnownObjectMode.Singleton);

同一類型對象隻可以用一種方式注冊(客戶激活 或者 服務激活)。即是說如果使用上面的方法注冊對象,那麼要麼調用 ClientActivated(),要麼調用ServerActivatedSingleCall()或者ServerActivatedSingleton(),而不能都調用。上面的RegisterWellKnownServiceType()方法接受三個參數:1.允許進行遠端通路的對象類型資訊;2.遠端對象的名稱,用于定位遠端對象;3.服務激活對象的方式,Singleton或者Single Call。

2.3 對象位置

經過上面兩步,我們已經開啟了通道,并注冊了對象(告訴了.Net哪個類型允許遠端通路)。那麼用戶端如何知道遠端對象位置呢?如同Web頁面有一個Url一樣,遠端對象也有一個Url,這個Url提供了遠端對象的位置。客戶程式通過這個Url來獲得遠端對象。

RemotingConfiguration類型還有一個ApplicationName靜态屬性,當設定了這個屬性之後,對于客戶激活對象,可以提供此ApplicationName作為Url參數,也可以不提供。如果提供ApplicationName,則必須與服務端設定的ApplicationName相比對;對于服務激活對象,通路時必須提供ApplicationName,此時兩種方式的Uri為下面的形式:

protocal://hostadrress:port/ApplicationName/ObjectUrl       // Server Activated

protocal://hostadrress:port                     // Client Activated Object

protocal:// hostadrress:port/ApplicationName    // Client Activated Object

比如,如果通道采用協定為tcp,伺服器位址為127.0.0.1,端口号為8051,ApplicationName設為DemoApp,ObjectUrl設為RemoteObject(ObjUrl為使用RegisterWellKnownServiceType()方法注冊服務激活對象時第2個參數所提供的字元串;注意客戶激活對象不使用它),則用戶端在通路時需要提供的位址為:

tcp://127.0.0.1:8051/DemoApp/RemoteObject   // Server Activated Object

tcp://127.0.0.1:8051/DemoApp                // Client Activated Object

tcp://127.0.0.1:8051                        // Client Activated Object

如果RemotingConfiguration類型沒有設定ApplicationName靜态屬性,則用戶端在擷取遠端對象時不需要提供ApplicationName,此時Url變為下面形式:

protocal://hostadrress:port/ObjectUrl       // Server Activated Object

protocal://hostadrress:port                 // Client Activated Object

3.客戶應用程式

我們現在再建立一個空解決方案ClientSide,然後在其下添加一個控制台應用程式ClientConsole,因為用戶端需要ServerAssembly.DemoClass的元資訊來建立代理,是以我們仍要添加對ServerAssembly項目的引用。除此以外,我們依然要添加 System.Runtime.Remoting程式集。

客戶應用程式的任務隻有一個:擷取遠端對象,調用遠端對象服務。記得客戶應用程式實際上獲得的隻是一個代理,隻是感覺上和遠端對象一模一樣。用戶端獲得對象有大緻下面幾種情況:

3.1使用new操作符建立遠端對象

客戶應用程式可以直接使用new獲得一個遠端對象。例如下面語句:

DemoClass obj = new DemoClass();

看到這裡你可能很驚訝,這樣的話不是和通常的建立對象沒有差別,為什麼建立的是遠端對象(這裡用“遠端對象”,隻是為了說明友善,要記得實際上是代理對象)而非本地對象呢(注意本地客戶程式ClientConsole也引用了ServerAssembly項目)?其實.Net和你一樣,它也不知道這裡要建立的是遠端對象,是以,在使用new建立遠端對象之前,我們首先要

注冊對象

。注冊對象的目的是告訴.Net,這個類型的對象将在遠端建立,同時還要告訴.Net遠端對象的位置。

我們知道遠端對象有 客戶激活 和 服務激活 兩種可能,是以客戶程式注冊也分為了兩種情況 -- 注冊客戶激活對象,注冊服務激活對象。在用戶端注冊對象也是通過RemotingConfiguration類型來完成:

// 注冊客戶激活對象

    // 下面兩個 url 任選一個

    string url = "tcp://127.0.0.1:8501";   

    //string url = "tcp://127.0.0.1:8501/SimpleRemote";

    RemotingConfiguration.RegisterActivatedClientType(t, url);

// 注冊服務激活對象

private static void ServerActivated() {

    string url = "tcp://127.0.0.1:8501/SimpleRemote/ServerActivated";

    RemotingConfiguration.RegisterWellKnownClientType(t, url);

我們看到,盡管在服務端,服務激活有兩種可能的方式,Singleton和SingleCall,但是在用戶端,服務激活的兩種方式采用同一個方法RegisterWellKnownClientType()方法進行注冊。是以我們可以說

服務端決定服務激活對象的運作方式(Singleton或SingleCall)。

3.2 其它建立遠端對象的方法

當我們在用戶端對遠端對象進行注冊之後,可以直接使用new操作符建立對象。如果不進行注冊來建立遠端對象,可以通過 RemotingServices.Connect()、Activator.GetObject()、Activator.CreateInstance()方法來完成:

string url = "tcp://127.0.0.1:8501/SimpleRemote/ServerActivated";

// 方式1

DemoClass obj = (DemoClass)RemotingServices.Connect(typeof(DemoClass), url);

// 方式2

DemoClass obj = (DemoClass)Activator.GetObject(typeof(DemoClass), url);

// 方式3

object[] activationAtt = { new UrlAttribute(url) };

DemoClass obj = (DemoClass)Activator.CreateInstance(typeof(DemoClass), null, activationAtt);

這幾種方法,RemotingServices.Connect()和Activator.GetObject()是最簡單也較為常用的,它們都是隻能建立服務激活對象,且建立的對象隻能有無參數的構造函數,并且在獲得對象的同時建立代理。Activator.CreateInstance()提供了多達13個重載方法,允許建立客戶激活對象,也允許使用有參數的構造函數建立對象,并且可以先傳回一個Wrapper(包裝)狀态的對象,然後在以後需要的時候通過UnWrap()方法建立代理。CreateInstance()方法更詳細的内容可以參考MSDN。

4.程式運作測試

Remoting 最讓初學者感到困惑的一個方面就是 客戶激活 與 服務激活 有什麼不同,什麼時候應該使用那種方式。說明它們之間的不同的最好方式就是通過下面幾個範例來說明,現在我們來将上面的服務端方法、用戶端方法分别進行一下組裝,然後進行一下測試(注意在運作用戶端之前必須保證服務端已經運作):

4.1 客戶激活方式

先看下客戶激活方式,服務端的Main()代碼如下:

static void Main(string[] args) {

    RegisterChannel();          // 注冊通道

    ClientActivated();          // 客戶激活方式

    Console.WriteLine("服務開啟,可按任意鍵退出...\n");

    Console.ReadKey();

用戶端的Main()代碼如下:

    // 注冊遠端對象

    ClientActivated();      // 客戶激活方式

    RunTest("Jimmy", "Zhang");

    RunTest("Bruce", "Wang");

    Console.WriteLine("用戶端運作結束,按任意鍵退出...");

private static void RunTest(string firstName, string familyName) {

    DemoClass obj = new DemoClass();

    obj.ShowAppDomain();

    obj.ShowCount(firstName);

    Console.WriteLine("{0}, the count is {1}.\n",firstName, obj.GetCount());

    obj.ShowCount(familyName);

    Console.WriteLine("{0}, the count is {1}.",familyName, obj.GetCount());

程式運作的結果如下:

.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2
.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2

其中第一幅圖是服務端,第二幅圖是用戶端,我們起碼可以得出下面幾個結論:

  1. 不管是對象的建立,還是對象方法的執行,都在服務端(遠端)執行。
  2. 服務端為每一個用戶端(兩次RunTest()調用,各建立了一個對象)建立其專屬的對象,為這個客戶提供服務,并且 儲存狀态 (第二次調用ShowCount()的值基于第一次調用ShowCount()之後count的值)。
  3. 可以從遠端擷取到方法執行的傳回值。(用戶端從GetCount()方法獲得了傳回值)

上面的第3點看起來好像是理所當然的,如果是調用本地對象的方法,那麼确實是顯而易見的。但是對于遠端來說,就存在一個很大的問題:遠端對象如何知道是誰在調用它?方法執行完畢,将傳回值發送給哪個客戶呢?此時可以回顧一下第一篇所提到的,用戶端在建立遠端對象時,已經将自己的位置通過消息發送給了遠端。

最後我們再進行一個深入測試,追蹤對象是在調用new時建立,還是在方法調用時建立。将RunTest()隻保留一行代碼:

    DemoClass obj = new DemoClass();    // 建立對象

然後再次運作程式,得到的輸出分别如下:

// 服務端

方式: Client Activated Object

服務端開啟,按任意鍵退出...

======= DomoClass Constructor =======

// 用戶端

用戶端運作結束,按任意鍵退出...

由此可以得出結論:使用客戶激活方式時,遠端對象在調用new操作時建立。

4.2 服務激活方式 -- Singleton

我們再來看一下服務激活的Singleton方式。先看服務端代碼(“按任意鍵退出”等提示語句均以省略,下同):

    RegisterChannel();              // 注冊通道

    ServerActivatedSingleton();     // Singleton方式

再看下用戶端的Main()方法:

    ServerActivated();     

程式的運作結果如下:

.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2
.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2

同上面一樣,第一幅為服務端,第二幅圖為用戶端。從圖中我們可以得出:

當使用Singleton模式時,服務端在第一次請求時建立一個對象(構造函數隻調用了一次)。對于後繼的請求僅使用這個對象進行服務(即使再次調用構造函數也不會建立對象),同時多個用戶端共享同一個對象的狀态(ShowCount()的值累加)。

我們和上一小節一樣,再次将用戶端的RunTest()隻保留為“DemoClass obj = new DemoClass(); ”一行語句,然後運作程式,得到的結果為:

方式: Server Activated Singleton

這個結果出乎我們意料,但它又向我們揭示了Singleton的另一個性質:

即使使用new操作符,用戶端也無法建立一個對象,而隻有在對象上第一次調用方法時才會建立。

仔細考慮一下這個和上面的結論是類似的,隻是更深入了一步。

4.3 服務激活方式 -- SingleCall

最後我們看一下SingleCall方式,注意到用戶端的代碼不需要做任何修改,是以我們隻需要切換一下服務端的激活方式就可以了:

    ServerActivatedSingleCall();

我們再次看一下運作結果:

.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2
.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2

我們可能首先驚訝構造函數居然調用了有10次之多,在每次RunTest()方法中各調用了5次。如同前面所說,對于SingleCall方式來說,對象對每一次方法調用提供服務,換言之,

對于每一次方法調用,建立一個全新的對象為其服務,在方法執行完畢後銷毀對象。

我們再看下用戶端的輸出:GetCount()方法全部傳回0,現在也很明确了,因為每次方法調用都會建立新對象(在建立對象時,int類型的count被賦預設值0),是以SingleCall方式是不會儲存對象狀态的。如果想要為對象儲存狀态,那麼需要另外的機制,比如将狀态存儲到對象之外:

public void ShowCount(string name, object clientId) {

    LoadStatus(this, clientId);         // 加載對象狀态

    count++;

    Console.WriteLine("{0},the count is {1}.", name, count);

    SaveStatus(this, clientId);         // 存儲對象狀态

其中LoadStatus()、SaveStatus()方法分别用于加載對象狀态和 存儲對象狀态。注意到ShowCount()方法多了一個clientId參數,這個參數用于标示客戶程式的id,因為服務端需要知道目前是為哪個客戶程式加載狀态。

最後,我們再次進行一下上面兩節将RunTest()隻保留為建立對象的一行代碼,得到的運作結果和Singleton是一樣的:

這說明使用SingleCall時,即使使用了new 來建立對象,也不會調用構造函數,隻有在調用方法時才會建立對象(調用了構造函數)。

Remoting中的傳值封送

很多朋友可能此刻會感到些許困惑,在Part.1的範例中,我們講述AppDomain時,使用了傳值封送和傳引用封送兩種方式,但是上面的三種激活方式都屬于傳引用封送。那麼如何進行對象的傳值封送呢(将DemoClass直接傳到本地)?實際上,在上面的例子中,我們已經進行了傳值封送,這個過程發生在我們在用戶端調用 GetCount() 時。為什麼呢?想一想,count值本來是位于服務端的,且int為可序列化對象(Serializable),在向用戶端傳回方法結果時,count值被包裝為了消息,然後由服務端發送回了用戶端,最後在用戶端進行了解包裝及還原狀态。

為了看得更清楚一些,我們在ServerAssembly中再建立一個DemoCount類型,然後對這個類型進行傳值封送,因為DemoCount僅僅是為了傳送資料,不包含任何行為,是以我們将它聲明為結構:

public class DemoClass : MarshalByRefObject {

    // 其餘方法略...

    // 示範傳值封送

    public DemoCount GetNewCount() {

        return new DemoCount(count);

[Serializable]

public struct DemoCount {

    private readonly int count;

    public DemoCount(int count) {

        this.count = count;

    public int Count {

        get { return count; }

在DemoClass中,我們又添加一個方法,它根據count的值建立了DemoCount對象,而DemoCount對象會通過傳值封送傳遞到用戶端。

現在修改用戶端,再重載一個RunTest()方法,用來測試這次的傳值封送:

// 測試傳值封送

private static void RunTest() {

    obj.ShowAppDomain();                // 顯示遠端對象所在應用程式域

    obj.ShowCount("張子陽");     // Count = 1

    DemoCount myCount = obj.GetNewCount();   // 傳值封送DemoCount

    myCount.ShowAppDomain();        // 顯示DemoCount所在應用程式域

    // 在用戶端顯示count值

    Console.WriteLine("張子陽, count: {0}.", myCount.Count);

此時我們再次進行測試,得到的結果如下:

.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2
.Net Remoting(基本操作) - Part.2.Net Remoting(基本操作) - Part.2

可以看到,我們在用戶端DemoCount上調用ShowAppDomain()方法時,傳回了ClientApp.exe,可見DemoCount已經通過傳值封送傳遞到了用戶端。那麼我們繼續上面的問題,如何将DemoClass整個傳值封送過來呢?首先,我認為沒有這個必要,如果将服務對象整個封送到用戶端來執行,那麼Remoting還有什麼意義呢?其次,我們來看如何實作它。方法很簡單,

我們建立一個工廠類作為遠端服務對象,然後将我們實際要傳值封送到用戶端的對象(比如DemoClass),作為工廠方法的傳回值。

這個例子我就不再示範了,相信看過上面的示例,您已經明白了。

繼續閱讀