Microsoft .Net Remoting系列專題之一:.Net Remoting基礎篇

javaprogramers發表於2006-05-14

Microsoft .Net Remoting系列專題之一

一、Remoting基礎

什麼是Remoting,簡而言之,我們可以將其看作是一種分散式處理方式。從微軟的產品角度來看,可以說Remoting就是DCOM的一種升級,它改善了很多功能,並極好的融合到.Net平臺下。Microsoft® .NET Remoting 提供了一種允許物件通過應用程式域與另一物件進行互動的框架。這也正是我們使用Remoting的原因。為什麼呢?在Windows作業系統中,是將應用程式分離為單獨的程式。這個程式形成了應用程式程式碼和資料周圍的一道邊界。如果不採用程式間通訊(RPC)機制,則在一個程式中執行的程式碼就不能訪問另一程式。這是一種作業系統對應用程式的保護機制。然而在某些情況下,我們需要跨過應用程式域,與另外的應用程式域進行通訊,即穿越邊界。

在Remoting中是通過通道(channel)來實現兩個應用程式域之間物件的通訊的。如圖所示:

首先,客戶端通過Remoting,訪問通道以獲得服務端物件,再通過代理解析為客戶端物件。這就提供一種可能性,即以服務的方式來發布伺服器物件。遠端物件程式碼可以執行在伺服器上(如伺服器啟用的物件和客戶端啟用的物件),然後客戶端再通過Remoting連線伺服器,獲得該服務物件並通過序列化在客戶端執行。

在Remoting中,對於要傳遞的物件,設計者除了需要了解通道的型別和埠號之外,無需再瞭解資料包的格式。但必須注意的是,客戶端在獲取伺服器端物件時,並不是獲得實際的服務端物件,而是獲得它的引用。這既保證了客戶端和伺服器端有關物件的鬆散耦合,同時也優化了通訊的效能。

1、Remoting的兩種通道

Remoting的通道主要有兩種:Tcp和Http。在.Net中,System.Runtime.Remoting.Channel中定義了IChannel介面。IChannel介面包括了TcpChannel通道型別和Http通道型別。它們分別對應Remoting通道的這兩種型別。

TcpChannel型別放在名字空間System.Runtime.Remoting.Channel.Tcp中。Tcp通道提供了基於Socket的傳輸工具,使用Tcp協議來跨越Remoting邊界傳輸序列化的訊息流。TcpChannel型別預設使用二進位制格式序列化訊息物件,因此它具有更高的傳輸效能。HttpChannel型別放在名字空間System.Runtime.Remoting.Channel.Http中。它提供了一種使用Http協議,使其能在Internet上穿越防火牆傳輸序列化訊息流。預設情況下,HttpChannel型別使用Soap格式序列化訊息物件,因此它具有更好的互操作性。通常在區域網內,我們更多地使用TcpChannel;如果要穿越防火牆,則使用HttpChannel。

2、遠端物件的啟用方式

在訪問遠端型別的一個物件例項之前,必須通過一個名為Activation的程式建立它並進行初始化。這種客戶端通過通道來建立遠端物件,稱為物件的啟用。在Remoting中,遠端物件的啟用分為兩大類:伺服器端啟用和客戶端啟用。

(1) 伺服器端啟用,又叫做WellKnow方式,很多又翻譯為知名物件。為什麼稱為知名物件啟用模式呢?是因為伺服器應用程式在啟用物件例項之前會在一個眾所周知的統一資源識別符號(URI)上來發布這個型別。然後該伺服器程式會為此型別配置一個WellKnown物件,並根據指定的埠或地址來發布物件。.Net Remoting把伺服器端啟用又分為SingleTon模式和SingleCall模式兩種。

SingleTon模式:此為有狀態模式。如果設定為SingleTon啟用方式,則Remoting將為所有客戶端建立同一個物件例項。當物件處於活動狀態時,SingleTon例項會處理所有後來的客戶端訪問請求,而不管它們是同一個客戶端,還是其他客戶端。SingleTon例項將在方法呼叫中一直維持其狀態。舉例來說,如果一個遠端物件有一個累加方法(i=0;++i),被多個客戶端(例如兩個)呼叫。如果設定為SingleTon方式,則第一個客戶獲得值為1,第二個客戶獲得值為2,因為他們獲得的物件例項是相同的。如果熟悉Asp.Net的狀態管理,我們可以認為它是一種Application狀態。

SingleCall模式:SingleCall是一種無狀態模式。一旦設定為SingleCall模式,則當客戶端呼叫遠端物件的方法時,Remoting會為每一個客戶端建立一個遠端物件例項,至於物件例項的銷燬則是由GC自動管理的。同上一個例子而言,則訪問遠端物件的兩個客戶獲得的都是1。我們仍然可以借鑑Asp.Net的狀態管理,認為它是一種Session狀態。

(2) 客戶端啟用。與WellKnown模式不同,Remoting在啟用每個物件例項的時候,會給每個客戶端啟用的型別指派一個URI。客戶端啟用模式一旦獲得客戶端的請求,將為每一個客戶端都建立一個例項引用。SingleCall模式和客戶端啟用模式是有區別的:首先,物件例項建立的時間不一樣。客戶端啟用方式是客戶一旦發出呼叫的請求,就例項化;而SingleCall則是要等到呼叫物件方法時再建立。其次,SingleCall模式啟用的物件是無狀態的,物件生命期的管理是由GC管理的,而客戶端啟用的物件則有狀態,其生命週期可自定義。其三,兩種啟用模式在伺服器端和客戶端實現的方法不一樣。尤其是在客戶端,SingleCall模式是由GetObject()來啟用,它呼叫物件預設的建構函式。而客戶端啟用模式,則通過CreateInstance()來啟用,它可以傳遞引數,所以可以呼叫自定義的建構函式來建立例項。

二、遠端物件的定義

前面講到,客戶端在獲取伺服器端物件時,並不是獲得實際的服務端物件,而是獲得它的引用。因此在Remoting中,對於遠端物件有一些必須的定義規範要遵循。

由於Remoting傳遞的物件是以引用的方式,因此所傳遞的遠端物件類必須繼承MarshalByRefObject。MSDN對MarshalByRefObject的說明是:MarshalByRefObject 是那些通過使用代理交換訊息來跨越應用程式域邊界進行通訊的物件的基類。不是從 MarshalByRefObject 繼承的物件會以隱式方式按值封送。當遠端應用程式引用一個按值封送的物件時,將跨越遠端處理邊界傳遞該物件的副本。因為您希望使用代理方法而不是副本方法進行通訊,因此需要繼承MarshallByRefObject。

以下是一個遠端物件類的定義:
public class ServerObject:MarshalByRefObject
{
        public Person GetPersonInfo(string name,string sex,int age)
        {
            Person person = new Person();
            person.Name = name;
            person.Sex = sex;
            person.Age = age;
            return person;
        }
}

這個類只實現了最簡單的方法,就是設定一個人的基本資訊,並返回一個Person類物件。注意這裡返回的Person類。由於這裡所傳遞的Person則是以傳值的方式來完成的,而Remoting要求必須是引用的物件,所以必須將Person類序列化。

因此,在Remoting中的遠端物件中,如果還要呼叫或傳遞某個物件,例如類,或者結構,則該類或結構則必須實現序列化Attribute[SerializableAttribute]:
[Serializable]
 public class Person
 {
        public Person()
        {
           
        }

        private string name;
        private string sex;
        private int age;

        public string Name
        {
            get    {return name;}
            set    {name = value;}
        }

        public string Sex
        {
            get {return sex;}
            set {sex = value;}
        }

        public int Age
        {
            get {return age;}
            set {age = value;}
        }
  }
將該遠端物件以類庫的方式編譯成Dll。這個Dll將分別放在伺服器端和客戶端,以新增引用。

在Remoting中能夠傳遞的遠端物件可以是各種型別,包括複雜的DataSet物件,只要它能夠被序列化。遠端物件也可以包含事件,但伺服器端對於事件的處理比較特殊,我將在本系列之三中介紹。

三、伺服器端

根據第一部分所述,根據啟用模式的不同,通道型別的不同伺服器端的實現方式也有所不同。大體上說,伺服器端應分為三步:

1、註冊通道

要跨越應用程式域進行通訊,必須實現通道。如前所述,Remoting提供了IChannel介面,分別包含TcpChannel和HttpChannel兩種型別的通道。這兩種型別除了效能和序列化資料的格式不同外,實現的方式完全一致,因此下面我們就以TcpChannel為例。

註冊TcpChannel,首先要在專案中新增引用“System.Runtime.Remoting”,然後using名字空間:System.Runtime.Remoting.Channel.Tcp。程式碼如下:
            TcpChannel channel = new TcpChannel(8080);
            ChannelServices.RegisterChannel(channel);

在例項化通道物件時,將埠號作為引數傳遞。然後再呼叫靜態方法RegisterChannel()來註冊該通道物件即可。

2、註冊遠端物件

註冊了通道後,要能啟用遠端物件,必須在通道中註冊該物件。根據啟用模式的不同,註冊物件的方法也不同。

(1) SingleTon模式

對於WellKnown物件,可以通過靜態方法RemotingConfiguration.RegisterWellKnownServiceType()來實現:RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObject),
                "ServiceMessage",WellKnownObjectMode.SingleTon);

(2)SingleCall模式

註冊物件的方法基本上和SingleTon模式相同,只需要將列舉引數WellKnownObjectMode改為SingleCall就可以了。RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObject),
                "ServiceMessage",WellKnownObjectMode.SingleCall);

(3)客戶端啟用模式

對於客戶端啟用模式,使用的方法又有不同,但區別不大,看了程式碼就一目瞭然。
RemotingConfiguration.ApplicationName = "ServiceMessage";
RemotingConfiguration.RegisterActivatedServiceType(
                typeof(ServerRemoteObject.ServerObject));

為什麼要在註冊物件方法前設定ApplicationName屬性呢?其實這個屬性就是該物件的URI。對於WellKnown模式,URI是放在RegisterWellKnownServiceType()方法的引數中,當然也可以拿出來專門對ApplicationName屬性賦值。而RegisterActivatedServiceType()方法的過載中,沒有ApplicationName的引數,所以必須分開。

3、登出通道

如果要關閉Remoting的服務,則需要登出通道,也可以關閉對通道的監聽。在Remoting中當我們註冊通道的時候,就自動開啟了通道的監聽。而如果關閉了對通道的監聽,則該通道就無法接受客戶端的請求,但通道仍然存在,如果你想再一次註冊該通道,會丟擲異常。

           //獲得當前已註冊的通道;
            IChannel[] channels = ChannelServices.RegisteredChannels;

            //關閉指定名為MyTcp的通道;
            foreach (IChannel eachChannel in channels)
            {
                if (eachChannel.ChannelName == "MyTcp")
                {
                    TcpChannel tcpChannel = (TcpChannel)eachChannel;

                    //關閉監聽;
                    tcpChannel.StopListening(null);

                    //登出通道;
                    ChannelServices.UnregisterChannel(tcpChannel);
                }
            }
程式碼中,RegisterdChannel屬性獲得的是當前已註冊的通道。在Remoting中,是允許同時註冊多個通道的,這一點會在後面說明。

四、客戶端

客戶端主要做兩件事,一是註冊通道。這一點從圖一就可以看出,Remoting中伺服器端和客戶端都必須通過通道來傳遞訊息,以獲得遠端物件。第二步則是獲得該遠端物件。

1、註冊通道:
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);

注意在客戶端例項化通道時,是呼叫的預設建構函式,即沒有傳遞埠號。事實上,這個埠號是缺一不可的,只不過它的指定被放在後面作為了Uri的一部分。

2、獲得遠端物件。

與伺服器端相同,不同的啟用模式決定了客戶端的實現方式也將不同。不過這個區別僅僅是WellKnown啟用模式和客戶端啟用模式之間的區別,而對於SingleTon和SingleCall模式,客戶端的實現完全相同。

(1) WellKnown啟用模式

要獲得伺服器端的知名遠端物件,可通過Activator程式的GetObject()方法來獲得:
ServerRemoteObject.ServerObject serverObj = (ServerRemoteObject.ServerObject)Activator.GetObject(
              typeof(ServerRemoteObject.ServerObject), "tcp://localhost:8080/ServiceMessage");

首先以WellKnown模式啟用,客戶端獲得物件的方法是使用GetObject()。其中引數第一個是遠端物件的型別。第二個引數就是伺服器端的uri。如果是http通道,自然是用http://localhost:8080/ServiceMessage了。因為我是用本地機,所以這裡是localhost,你可以用具體的伺服器IP地址來代替它。埠必須和伺服器端的埠一致。後面則是伺服器定義的遠端物件服務名,即ApplicationName屬性的內容。

(2) 客戶端啟用模式

如前所述,WellKnown模式在客戶端建立物件時,只能呼叫預設的建構函式,上面的程式碼就說明了這一點,因為GetObject()方法不能傳遞建構函式的引數。而客戶端啟用模式則可以通過自定義的建構函式來建立遠端物件。

客戶端啟用模式有兩種方法:
1) 呼叫RemotingConfiguration的靜態方法RegisterActivatedClientType()。這個方法返回值為Void,它只是將遠端物件註冊在客戶端而已。具體的例項化還需要呼叫物件類的建構函式。
 RemotingConfiguration.RegisterActivatedClientType(               
                typeof(ServerRemoteObject.ServerObject),
                "tcp://localhost:8080/ServiceMessage");
 ServerRemoteObject.ServerObject serverObj = new ServerRemoteObject.ServerObject();

2) 呼叫程式Activator的CreateInstance()方法。這個方法將建立方法引數指定型別的類物件。它與前面的GetObject()不同的是,它要在客戶端呼叫建構函式,而GetObject()只是獲得物件,而建立例項是在伺服器端完成的。CreateInstance()方法有很多個過載,我著重說一下其中常用的兩個。
a、 public static object CreateInstance(Type type, object[] args, object[] activationAttributes);

引數說明:
type:要建立的物件的型別。
args :與要呼叫建構函式的引數數量、順序和型別匹配的引數陣列。如果 args 為空陣列或空引用(Visual Basic 中為 Nothing),則呼叫不帶任何引數的建構函式(預設建構函式)。
activationAttributes :包含一個或多個可以參與啟用的屬性的陣列。

這裡的引數args是一個object[]陣列型別。它可以傳遞要建立物件的建構函式中的引數。從這裡其實可以得到一個結論:WellKnown啟用模式所傳遞的遠端物件類,只能使用預設的建構函式;而Activated模式則可以使用者自定義建構函式。activationAttributes引數在這個方法中通常用來傳遞伺服器的url。
假設我們的遠端物件類ServerObject有個建構函式:
            ServerObject(string pName,string pSex,int pAge)
            {
                name = pName;
                sex = pSex;
                age = pAge;
            }

那麼實現的程式碼是:
            object[] attrs = {new UrlAttribute("tcp://localhost:8080/ServiceMessage")};
            object[] objs = new object[3];
            objs[0] = "wayfarer";
            objs[1] = "male";
            objs[2] = 28;
            ServerRemoteObject.ServerObject = Activator.CreateInstance(
                typeof(ServerRemoteObject.ServerObject),objs,attrs);
可以看到,objs[]陣列傳遞的就是建構函式的引數。

b、public static ObjectHandle CreateInstance(string assemblyName, string typeName, object[] activationAttribute);

引數說明:
assemblyName :將在其中查詢名為 typeName 的型別的程式集的名稱。如果 assemblyName 為空引用(Visual Basic 中為 Nothing),則搜尋正在執行的程式集。
typeName:首選型別的名稱。
activationAttributes :包含一個或多個可以參與啟用的屬性的陣列。

引數說明一目瞭然。注意這個方法返回值為ObjectHandle型別,因此程式碼與前不同:
            object[] attrs = {new UrlAttribute("tcp://localhost:8080/EchoMessage")};           
            ObjectHandle handle = Activator.CreateInstance("ServerRemoteObject",
                                   "ServerRemoteObject.ServerObject",attrs);
            ServerRemoteObject.ServerObject obj = (ServerRemoteObject.ServerObject)handle.Unwrap();

這個方法實際上是呼叫的預設建構函式。ObjectHandle.Unwrap()方法是返回被包裝的物件。

說明:要使用UrlAttribute,還需要在名稱空間中新增:using System.Runtime.Remoting.Activation;

五、Remoting基礎的補充

通過上面的描述,基本上已經完成了一個最簡單的Remoting程式。這是一個標準的建立Remoting程式的方法,但在實際開發過程中,我們遇到的情況也許千奇百怪,如果只掌握一種所謂的“標準”,就妄想可以“一招鮮、吃遍天”,是不可能的。

1、註冊多個通道

在Remoting中,允許同時建立多個通道,即根據不同的埠建立不同的通道。但是,Remoting要求通道的名字必須不同,因為它要用來作為通道的唯一識別符號。雖然IChannel有ChannelName屬性,但這個屬性是隻讀的。因此前面所述的建立通道的方法無法實現同時註冊多個通道的要求。

這個時候,我們必須用到System.Collection中的IDictionary介面:

註冊Tcp通道:
IDictionary tcpProp = new Hashtable();
tcpProp["name"] = "tcp9090";
tcpProp["port"] = 9090;
IChannel channel = new TcpChannel(tcpProp,
 new BinaryClientFormatterSinkProvider(),
 new BinaryServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel);

註冊Http通道:
IDictionary httpProp = new Hashtable();
httpProp["name"] = "http8080";
httpProp["port"] = 8080;
IChannel channel = new HttpChannel(httpProp,
 new SoapClientFormatterSinkProvider(),
 new SoapServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel);

在name屬性中,定義不同的通道名稱就可以了。

2、遠端物件後設資料相關性

由於伺服器端和客戶端都要用到遠端物件,通常的方式是生成兩份完全相同的物件Dll,分別新增引用。不過為了程式碼的安全性,且降低客戶端對遠端物件後設資料的相關性,我們有必要對這種方式進行改動。即在伺服器端實現遠端物件,而在客戶端則刪除這些實現的後設資料。

由於啟用模式的不同,在客戶端建立物件的方法也不同,所以要分離後設資料的相關性,也應分為兩種情況。

(1) WellKnown啟用模式:

通過介面來實現。在伺服器端,提供介面和具體類的實現,而在客戶端僅提供介面:
    public interface IServerObject
    {
        Person GetPersonInfo(string name,string sex,int age);
    }

public class ServerObject:MarshalByRefObject,IServerObject
{ ......}
注意:兩邊生成該物件程式集的名字必須相同,嚴格地說,是名稱空間的名字必須相同。
           
(2) 客戶端啟用模式:

如前所述,對於客戶端啟用模式,不管是使用靜態方法,還是使用CreateInstance()方法,都必須在客戶端呼叫建構函式例項化物件。所以,在客戶端我們提供的遠端物件,就不能只提供介面,而沒有類的實現。實際上,要做到與遠端物件後設資料的分離,可以由兩種方法供選擇:

a、利用WellKnown啟用模式模擬客戶端啟用模式:

方法是利用設計模式中的“抽象工廠”,下面的類圖表描述了總體解決方案:

我們在伺服器端的遠端物件中加上抽象工廠的介面和實現類:
    public interface IServerObject
    {
        Person GetPersonInfo(string name,string sex,int age);
    }

    public interface IServerObjFactory
    {
        IServerObject CreateInstance();       
    }

    public class ServerObject:MarshalByRefObject,IServerObject
    {
        public Person GetPersonInfo(string name,string sex,int age)
        {
            Person person = new Person();
            person.Name = name;
            person.Sex = sex;
            person.Age = age;
            return person;
        }       
    }

    public class ServerObjFactory:MarshalByRefObject,IServerObjFactory
    {
        public IServerObject CreateInstance()
        {
            return new ServerObject();
        }
    }

然後再客戶端的遠端物件中只提供工廠介面和原來的物件介面:
    public interface IServerObject
    {
        Person GetPersonInfo(string name,string sex,int age);
    }

    public interface IServerObjFactory
    {
        IServerObject CreateInstance();       
    }
我們用WellKnown啟用模式註冊遠端物件,在伺服器端:
           //傳遞物件;
            RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObjFactory),
                "ServiceMessage",WellKnownObjectMode.SingleCall);

注意這裡註冊的不是ServerObject類物件,而是ServerObjFactory類物件。

客戶端:
ServerRemoteObject.IServerObjFactory serverFactory =               
                (ServerRemoteObject.IServerObjFactory) Activator.GetObject(
                typeof(ServerRemoteObject.IServerObjFactory),
                "tcp://localhost:8080/ServiceMessage");

ServerRemoteObject.IServerObject serverObj = serverFactory.CreateInstance();

為什麼說這是一種客戶端啟用模式的模擬呢?從啟用的方法來看,我們是使用了SingleCall模式來啟用物件,但此時啟用的並非我們要傳遞的遠端物件,而是工廠物件。如果客戶端要建立遠端物件,還應該通過工廠物件的CreateInstance()方法來獲得。而這個方法正是在客戶端呼叫的。因此它的實現方式就等同於客戶端啟用模式。

b、利用替代類來取代遠端物件的後設資料

實際上,我們可以用一個trick,來欺騙Remoting。這裡所說的替代類就是這個trick了。既然是提供服務,Remoting傳遞的遠端物件其實現的細節當然是放在伺服器端。而要在客戶端放物件的副本,不過是因為客戶端必須呼叫建構函式,而採取的無奈之舉。既然具體的實現是在伺服器端,又為了能在客戶端例項化,那麼在客戶端就實現這些好了。至於實現的細節,就不用管了。

如果遠端物件有方法,伺服器端則提供方法實現,而客戶端就提供這個方法就OK了,至於裡面的實現,你可以是丟擲一個異常,或者return 一個null值;如果方法返回void,那麼裡面可以是空。關鍵是這個客戶端類物件要有這個方法。這個方法的實現,其實和方法的宣告差不多,所以我說是一個trick。方法如是,建構函式也如此。

還是用程式碼來說明這種“陰謀”,更直觀:

伺服器端:
    public class ServerObject:MarshalByRefObject
    {
        public ServerObject()
        {
           
        }

        public Person GetPersonInfo(string name,string sex,int age)
        {
            Person person = new Person();
            person.Name = name;
            person.Sex = sex;
            person.Age = age;
            return person;
        }       
    }

客戶端:
    public class ServerObject:MarshalByRefObject
    {
        public ServerObj()
        {
            throw new System.NotImplementedException();
        }

        public Person GetPersonInfo(string name,string sex,int age)
        {
            throw new System.NotImplementedException();
        }       
    }

比較客戶端和伺服器端,客戶端的方法GetPersonInfo(),沒有具體的實現細節,只是丟擲了一個異常。或者直接寫上語句return null,照樣OK。我們稱客戶端的這個類為遠端物件的替代類。

3、利用配置檔案實現

前面所述的方法,於伺服器uri、埠、以及啟用模式的設定是用程式碼來完成的。其實我們也可以用配置檔案來設定。這樣做有個好處,因為這個配置檔案是Xml文件。如果需要改變埠或其他,我們就不需要修改程式,並重新編譯,而是隻需要改變這個配置檔案即可。

(1) 伺服器端的配置檔案:
<configuration>
  <system.runtime.remoting>
    <application name="ServerRemoting">
      <service>
        <wellknown mode="Singleton" type="ServerRemoteObject.ServerObject" objectUri="ServiceMessage"/>
      </service>
      <channels>
         <channel ref="tcp" port="8080"/>
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

如果是客戶端啟用模式,則把wellknown改為activated,同時刪除mode屬性。

把該配置檔案放到伺服器程式的應用程式資料夾中,命名為ServerRemoting.config。那麼前面的伺服器端程式直接用這條語句即可:
RemotingConfiguration.Configure("ServerRemoting.config");

(2) 客戶端配置檔案

如果是客戶端啟用模式,修改和上面一樣。呼叫也是使用RemotingConfiguration.Configure()方法來呼叫儲存在客戶端的配置檔案。

配置檔案還可以放在machine.config中。如果客戶端程式是web應用程式,則可以放在web.config中。

4、啟動/關閉指定遠端物件

Remoting中沒有提供類似UnregisterWellKnownServiceType()的方法,也即是說,一旦通過註冊了遠端物件,如果沒有關閉通道的話,該物件就一直存在於通道中。只要客戶端啟用該物件,就會建立物件例項。如果Remoting傳送的只有一個遠端物件,這不存在問題,關閉通道就可以了。如果傳送多個遠端物件呢?要關閉指定的遠端物件應該怎麼做?關閉之後又需要啟動又該如何?

我們注意到在Remoting中提供了Marshal()和Disconnect()方法,答案就在這裡。Marshal()方法是將MarshalByRefObject類物件轉化為ObjRef類物件,這個物件是儲存生成代理以與遠端物件通訊所需的所有相關資訊。這樣就可以將該例項序列化以便在應用程式域之間以及通過網路進行傳輸,客戶端就可以呼叫了。而Disconnect()方法則將具體的例項物件從通道中斷開。

方法如下:
首先註冊通道:
TcpChannel channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel);

接著啟動服務:
先在伺服器端例項化遠端物件。
ServerObject obj = new ServerObject();

然後,註冊該物件。注意這裡不用RemotingConfiguration.RegisterWellKnownServiceType(),而是使用RemotingServices.Marshal():

ObjRef objrefWellKnown = RemotingServices.Marshal(obj, "ServiceMessage");

如果要登出物件,則:
RemotingServices.Disconnect(obj);

要注意,這裡Disconnect的類物件必須是前面例項化的物件。正因為此,我們可以根據需要建立指定的遠端物件,而關閉時,則Disconnect之前例項化的物件。

至於客戶端的呼叫,和前面WellKnown模式的方法相同,仍然是通過Activator.GetObject()來獲得。但從實現程式碼來看,我們會注意到一個問題,由於伺服器端是顯式的例項化了遠端物件,因此不管客戶端有多少,是否相同,它們呼叫的都是同一個遠端物件。因此我們將這個方法稱為模擬的SingleTon模式。

客戶端啟用模式

我們也可以通過Marshal()和Disconnect()來模擬客戶端啟用模式。首先我們來回顧“遠端物件後設資料相關性”一節,在這一節中,我說到採用設計模式的“抽象工廠”來建立物件例項,以此用SingleCall模式來模擬客戶端啟用模式。在仔細想想前面的模擬的SingleTon模式。是不是答案就將呼之欲出呢?

在“模擬的SingleTon”模式中,我們是將具體的遠端物件例項進行Marshal,以此讓客戶端獲得該物件的引用資訊。那麼我們換一種思路,當我們用抽象工廠提供介面,工廠類實現建立遠端物件的方法。然後我們在伺服器端建立工廠類例項。再將這個工廠類例項進行Marshal。而客戶端獲取物件時,不是獲取具體的遠端物件,而是獲取具體的工廠類物件。然後再呼叫CreateInstance()方法來建立具體的遠端物件例項。此時,對於多個客戶端而言,呼叫的是同一個工廠類物件;然而遠端物件是在各個客戶端自己建立的,因此對於遠端物件而言,則是由客戶端啟用,建立的是不同物件了。

當我們要啟動/關閉指定物件時,只需要用Disconnet()方法來登出工廠類物件就可以了。

六、小結

Microsoft.Net Remoting真可以說是博大精深。整個Remoting的內容不是我這一篇小文所能盡述的,更不是我這個Remoting的初學者所能掌握的。王國維在《人間詞話》一書中寫到:古今之成大事業大學問者,必經過三種境界。“昨夜西風凋碧樹,獨上高樓,望盡天涯路。”此第一境界也。“衣帶漸寬終不悔,為伊消得人憔悴。”此第二境界也。“眾裡尋他千百度,驀然回首,那人卻在燈火闌珊處。”此第三境界也。如以此來形容我對Remoting的學習,還處於“獨上高樓,望盡天涯路”的時候,真可以說還未曾登堂入室。

或許需得“衣帶漸寬”,學得Remoting“終不悔”,方才可以“驀然回首”吧。

相關文章