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),則調用不帶任何參數的構造函數(
默認構造函數)。
這里的參數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程序的方法,但在實際開發過程中,我們遇到的情況也許千奇百怪,如果只掌握一種所謂的“標准”,就妄想可以“一招鮮、吃遍天”,是不可能的。
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“終不悔”,方才可以“驀然回首” 吧。