Remoting基礎
基本原理
當客戶端創建遠程RemotableClass的一個實例,.NET框架在客戶端應用程序域中產生一個代理。該代理看起來就像實際對象。代理收到調用後,通過通道連接到遠程的對象。
一、編寫步驟
第一步
編寫一個DLL,其中包含所要Remottable的類
public class RemotableClass:MarshalByRefObject
{
….
}
第二步
服務器進程註冊該remotable 類以便其他應用程序可以激活。根據該對象是如何激活,服務器通過兩種靜態方法來註冊:RegisterActivatedServiceType或者RegisterWellKnownServiceType。下面的語句使用RegisterWellKnownServiceType來註冊RemotableClass,以便遠程激活。
RemotingConfiguration.RegisterWellKnownServiceType(
Typeof(RemotableClass), //Remotable類
"RemoteObject", // Remotable類的URI
WellKnownObjectMode.SingleCall); //激活模式
第一個參數是指能遠程化的類。
第二個是指客戶端使用來激活對象的URI----也就是客戶端告訴服務器來激活
RemotableClass實例的URI。
第三個參數指定激活模式。有兩種選擇。WellKnownObjectMode.SingleCall是指爲客戶端的每一次調用創建一個新的實例。WellKnownObjectMode.Singleton是指創建一個RemotableClass實例來處理所有客戶端的調用。
第三步
爲了使客戶端可以使用RemotableClass,服務器進程必須創建,註冊一個通道。該通道提供對象和遠程客戶端交流的一個渠道。在服務器端,.NET框架提供了兩種通道:
System.Runtime.Remoting.Channels.Tcp.TcpServerChannel:可以接受遠程客戶端的TCP連接。
System.Runtime.Remoting.Channels.Http.HttpServerChannel:接受HTTP連接。
下面的語句創建一個在1234端口監聽的TcpServerChannel通道,並用.NET框架註冊:
TcpServerChannel channel = new TcpServerChannel(1234);
ChannelServices.RegisterChannel(channel);
下面的語句註冊了一個在1234端口監聽的HTTP通道:
HttpServiceChannel channel = new HttpServerChannel(1234);
ChannelServices.RegisterChannel(channel);
TcpServerChannel更有效率一點。HttpServerChannel是使用IIS作爲遠程激活代理時使用的選擇。
第四步
在客戶端要想創建遠程類的一個實例,也必須做一些註冊。
第一必須註冊一個客戶端通道。.NET框架提供了兩種類型的客戶端通道:TcpClientChannel和HttpClientChannel,分別和服務器端通道相對應。
第二,如果客戶端想使用new操作符來生產遠程對象,必須將遠程對象註冊到本地應用程序域。
RemotingConfiguration.RegisterWellKnownClientType是在客戶端註冊一個類。
RemotingConfiguration.RegisterWellKnownServiceType是在服務器上註冊一個類。
下面的代碼在客戶端註冊了一個TCP通道,而且也將RemotableClass註冊到本地應用程序域中:
TcpClientChannel channel = new TcpClientChannel();
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownClientType( Typeof(RemotableClass), "tcp://localhost:1234/RemoteObject");
第二個參數是指遠程對象的URL。
協議必須匹配應用程序註冊的通道協議。
可以使用機器名或者IP地址來替換localhost。
端口數必須好服務器端監聽的端口數一樣。
對象URI,必須和服務器用RegisterWellKnownServiceType註冊的匹配。
第五步
在客戶端使用new來產生代理:
RemotableClass rc = new RemotableClass();
這個操作在客戶端應用程序域中產生一個代理,返回RemotableClass的一個引用。
二、實際範例
ClockServer.cs
using System;
public class Clock : MarshalByRefObject
{
public string GetCurrentTime ()
{
return DateTime.Now.ToLongTimeString ();
}
}
TimeServer.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
class MyApp
{
static void Main ()
{
TcpServerChannel channel = new TcpServerChannel (1234);
ChannelServices.RegisterChannel (channel);
RemotingConfiguration.RegisterWellKnownServiceType(typeof (Clock), "Clock", WellKnownObjectMode.SingleCall);
Console.WriteLine ("Press Enter to terminate...");
Console.ReadLine ();
}
}
TimeClient.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
class MyApp
{
static void Main ()
{
TcpClientChannel channel = new TcpClientChannel ();
ChannelServices.RegisterChannel (channel);
RemotingConfiguration.RegisterWellKnownClientType(typeof (Clock), "tcp://localhost:1234/Clock");
Clock clock = new Clock ();
Console.WriteLine (clock.GetCurrentTime ());
}
}
編譯:
1. csc /t:library clockserver.cs
2. csc /r:clockserver.dll timeserver.cs
3. csc /r:clockserver.dll timeclient.cs
要將ClockServer.dll拷貝到客戶端。因爲創建遠程對象的代理時,.NET框架需要描述Clock類的原數據。它可以從DLL中得到原數據。
三、配置方式
TimeServer和TimeClient在其源代碼內部註冊通道和遠程化的類。這樣有個缺點,一旦任何一個註冊數據改變,你必須要修改源代碼,並重新編譯。
這就是爲什麼.NET框架支持另一種形式的註冊。聲明註冊是通過調用靜態
RemotingConfiguration.Configure方法來從Config文件中得到信息。
範例如下:
ClockServer.cs
using System;
public class Clock : MarshalByRefObject
{
public string GetCurrentTime ()
{
return DateTime.Now.ToLongTimeString ();
}
}
TimeServer.cs
using System;
using System.Runtime.Remoting;
class MyApp
{
static void Main ()
{
RemotingConfiguration.Configure ("TimeServer.exe.config");
Console.WriteLine ("Press Enter to terminate...");
Console.ReadLine ();
}
}
TimeServer.exe.config
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown mode="SingleCall" type="Clock, ClockServer" objectUri="Clock" />
</service>
<channels>
<channel ref="tcp server" port="1234" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
TimeClient.cs
using System;
using System.Runtime.Remoting;
class MyApp
{
static void Main ()
{
RemotingConfiguration.Configure ("TimeClient.exe.config");
Clock clock = new Clock ();
Console.WriteLine (clock.GetCurrentTime ());
}
}
TimeClient.exe.config
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown type="Clock, ClockServer" url="tcp://localhost:1234/Clock" />
</client>
<channels>
<channel ref="tcp client" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
該方式的缺點是配置文件可以被修改和刪除。
四、激活方式
.NET框架將可遠程化對象分爲兩種:服務器激活對象和客戶端激活對象。服務器端激活對象是通過RemotingConfiguration’sRegisterWellKnownServiceType和
RegisterWellKnownClientType方法註冊的。上面的範例都是服務器端激活對象。客戶端激活對象是通過RegisterActivateServiceType和RegisterActivatedClientType註冊的。
服務器端激活對象被稱爲服務器激活的,因爲當客戶端使用new,只有一個代理被創建。實際對象知道通過代理來調用一個方法時才被創建(激活)。換句話說,不是客戶端決定什麼時候去創建物理上的真正對象。客戶端激活對象在客戶端使用new時就在服務器上創建。這個是第一個差別。
第二個差別是客戶端激活對象可以使用非缺省構造函數(帶參數的構造函數)激活。服務器端機會對象不支持非缺省構造函數,因爲使用new只是創建一個代理,並沒有創建對應的實際對象。客戶端激活對象可以通過new同時創建代理和對象。
第三個差別是客戶端和對象是如何聯繫在一起的。當註冊服務器激活對象時,你可以指定激活模式來決定爲每一個請求創建一個對象實例還是創建一個對象實例來服務所有的請求。這兩中激活模式是:
WellKnownObjectMode.SingleCall:爲每個請求創建一個唯一的對象實例。
WellKonwnObjectMode.Singleton:創建一個對象實例來服務所有的請求
通常根據環境來選擇合適的激活模式。舉例來說,如果一個遠程化對象提供了一個"One-shot"服務,不需要在多次調用間保持狀態或者不需要在所有客戶端同享狀態,那麼SingleCall是個正確的選擇。因爲每一次的請求產生的是一個新的對象實例。如果想在客戶端之間傳遞數據,則要使用Singleton。
Singleton對象一個值得注意的地方是線程的同步問題。當兩個客戶端同時調用該對象的方法時,可能會出現錯誤,這時要使用.NET框架提供的同步機制。
客戶端激活對象提供第三種選擇。當使用客戶端激活對象時,該對象僅爲此客戶端服務,可以在多次調用間保持狀態。
Single-call服務器激活對象,Singleton服務器激活對象和客戶端激活對象的提供了三種不同的激活模式。當不需要在所有客戶端共享狀態時,則使用Single-call。當要在所有客戶端共享狀態時則使用Singleton。當不需要所有的客戶端連接到同一個對象,只要保持該客戶端自己的狀態時,則使用客戶端激活對象。
程序範例:
Stopwatch.cs
using System;
public class Stopwatch : MarshalByRefObject
{
DateTime mark = DateTime.Now;
public void Start ()
{
mark = DateTime.Now;
}
public int Stop ()
{
return (int) ((DateTime.Now - mark).TotalMilliseconds);
}
}
StopwatchServer.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
class MyApp
{
static void Main ()
{
TcpServerChannel channel = new TcpServerChannel (1234);
ChannelServices.RegisterChannel (channel);
RemotingConfiguration.RegisterActivatedServiceType(typeof (Stopwatch));
Console.WriteLine ("Press Enter to terminate...");
Console.ReadLine ();
}
}
StopwatchClient.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
class MyApp
{
static void Main ()
{
TcpClientChannel channel = new TcpClientChannel ();
ChannelServices.RegisterChannel (channel);
RemotingConfiguration.RegisterActivatedClientType(typeof (Stopwatch), "tcp://localhost:1234");
Stopwatch sw = new Stopwatch ();
sw.Start ();
Console.WriteLine ("Press Enter to show elapsed time...");
Console.ReadLine ();
Console.WriteLine (sw.Stop () + " millseconds");
}
}
五、Activator.GetObject和Activator.CreateInstance方法
new操作符並不是激活遠程對象的唯一方法。.NET框架提供了其他的激活方法:GetObject和CreateInstance。它們都是System.Activator類的成員。GetObject被用來激活在服務器端激活的對象,而CreateInstance被用來激活在客戶端激活的對象。
當使用GetObject或者CreateInstance來激活遠程對象時,不再需要調用RegisterActivatedClientType或者RegisterWellKnownClientType來註冊服務器上可遠程化的類。例如:激活在服務器端激活的對象時:
RemotingConfiguration.RegisterWellKnownClientType(typeof(Clock),"tcp://localhost:1234/Clock");
Clock clock = new Clock();
可以使用下面的方法代
Clock clock =(Clock) Activator.GetObject(typeof(Clock,"tcp://localhost:1234/Clock");
激活客戶端對象時:
RemotingConfiguration.RegisterActivatedClientType(typeof(Stopwatch),"tcp://localhost:1234");
Stopwatch sw = new StopWatch();
可以這樣的方式:
object[] url ={new UrlAttribute("tcp://localhost:1234")};
Stopwatch sw =(Stopwatch) Activator.CreateInstance(typeof(Stopwatch),null,url);
爲什麼要使用它們來代替new呢?因爲在你僅知道URL和接口時,GetObject和CreateInstance可以仍使用。假設改變Clock類,它實現一個IClock接口。
使用GetObject時:
Iclock ic = (Iclock)Activator.GetObject(typeof(Iclock),"tcp://localhost:1234/Clock");
如果使用new,則會出現編譯錯誤,因爲new不能接受一個接口名稱:
RemotingConfiguration.RegisterWellKnownClientType(typeof (IClock), "tcp://localhost:1234/Clock");
IClock ic = new IClock ();
六、對象生存期和租用期
一個single-call服務器端激活對象只在方法調用期間生存。之後,被垃圾回收器標記爲刪除。Singleton 服務器激活對象和客戶端激活對象不一樣,他們的生存期被租用控制。租用是一個對象,它實現了定義在System.Runtime.Remoting.Lifetime名稱空間的Ilease接口。
Singleton 服務器端激活對象和客戶端激活對象缺省的租用對象有一個5分鐘的InitialLeaseTime,2分鐘的RenewOnCallTime,5分鐘的CurrentLeaseTime。如果對象沒有方法被調用,當CurrentLeaseTime爲0時它被清除,也就是5分鐘後被清除。