一、Remoting基礎
什麼是Remoting,簡而言之,我們可以将其看作是一種分布式處理方式。從微軟的産品角度來看,可以說Remoting就是DCOM的一種更新,它改善了很多功能,并極好的融合到.Net平台下。Microsoft® .NET Remoting 提供了一種允許對象通過應用程式域與另一對象進行互動的架構。這也正是我們使用Remoting的原因。為什麼呢?在Windows作業系統中,是将應用程式分離為單獨的程序。這個程序形成了應用程式代碼和資料周圍的一道邊界。如果不采用程序間通信(RPC)機制,則在一個程序中執行的代碼就不能通路另一程序。這是一種作業系統對應用程式的保護機制。然而在某些情況下,我們需要跨過應用程式域,與另外的應用程式域進行通信,即穿越邊界。
在Remoting中是通過通道(channel)來實作兩個應用程式域之間對象的通信的。如圖所示:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmLxcmbpR3btVmUvwlclJXYmlXY39CXt92YfN3ZvxmYuN2LcNXZnFWbp9CXt92YuM3ZvxmYuNmL3d3dvw1LcpDc0RHaiojIsJye.jpg)
首先,用戶端通過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“終不悔”,方才可以“蓦然回首”吧。