一、编写步骤

第一步 编写一个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的一个引用。

xiaoti 发布于2007-10-16 15:00:49

二、实际范例

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中得到原数据。

xiaoti 发布于2007-10-16 15:01:06

三、配置方式

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。当不需要所有的客户端连接到同一个对象,只要保持该客户端自己的状态时,则使用客户端激活对象。

xiaoti 发布于2007-10-16 15:01:20

程序范例:

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分钟后被清除。

xiaoti 发布于2007-10-16 15:01:45

一、 如何使用IIS作为激活代理

.NET Remoting和DCOM之间的一个区别是前者不支持自动运行的服务器进程。需要人工启动服务器进程来注册用来远程激活的类和监听请求。而对于DCOM,当远程客户端调用CoCreateInstanceEx或者其他激活API时,会自动运行服务器进程。

.NET remoting 提供了两种方法来避免人工启动服务器。第一个是将服务器应用程序当做一个服务来实现。可以编写一个从 System.ServiceProcess.Service派生的服务,重载其中关键的需方法例如OnStart和OnStop。将服务器当做一个服务来实现的好处是你可以配置该服务以便系统启动时能自动运行该服务。

第二个方法是使用IIS作为激活代理。IIS本身就是一个服务,在大多数Web Servers运行时会一直启动。而且IIS能够通过使用.NET Remoting机制来响应客户端激活对象的请求。使用IIS有以下几个好处:

1、不再需要编写一个用来注册可远程化的类和监听端口的服务器,IIS就是服务器。

2、可以使用IIS鉴别远程调用者,也可以使用SSL来保护数据。

3、可以使用IIS来管理端口。如果在一个机器上部署了两个传统的应用程序服务器,则需要你来保证这两个服务器使用不同的端口。使用IIS作为宿主,则IIS可以选择端口,这样可以简化发布和管理。

IIS支持服务器端激活对象和客户端激活对象。可以使用程序注册(在Global.asax中),也可以使用声明注册(在Web.config中)。

1、服务器端激活对象

下面的Web.config注册了使用IIS激活的Clock类:

<configuration> 
            <system.runtime.remoting> 
            <application> 
            <service> 
            <wellknown mode="SingleCall" type="Clock, ClockServer" objectUri="Clock.rem" /> 
            </service> 
            </application> 
            </system.runtime.remoting> 
            </configuration>


注意Clock的URI:Clock.rem。使用IIS注册的URI必须以.rem或者.soap结束,因为该扩展对应到IIS原数据中的Aspnet_isapi.dll和Machine.config中的.NET remoting 子系统。

使用IIS激活对象都是通过HTTP通道来与客户端进行通信。客户端必须注册HTTP通道。下面是一个客户端如何创建一个Clock实例,假设Clock在本地机器上一个叫MyClock的虚拟目录中。

HttpClientChannel channel = new HttpClientChannel (); 
ChannelServices.RegisterChannel (channel); 
RemotingConfiguration.RegisterWellKnownClientType 
(typeof (Clock),"http://localhost/MyClock/Clock.rem"); 
Clock clock = new Clock ();


注意服务器和客户端都没有指定端口,IIS选择该端口

2、客户端激活对象

Web.config文件注册注册一个客户端激活对象Clock

<configuration> 
<system.runtime.remoting> 
<application> 
<service> 
<activated type="Clock, ClockServer" /> 
</service> 
</application> 
</system.runtime.remoting> 
</configuration>

xiaoti 发布于2007-10-16 15:01:58

下面是客户端的写法(依然假设Clock在本地机器MyClock虚拟目录中):

HttpClientChannel channel = new HttpClientChannel (); 
            ChannelServices.RegisterChannel (channel); 
            RemotingConfiguration.RegisterActivatedClientType 
            (typeof (Clock), "http://localhost/MyClock"); 
            Clock clock = new Clock ();


注意:使用IIS必须在虚拟目录中有一个可远程化的类,而且必须把 Web.config放在单独的虚拟目录中(例如MyClock),把DLL放在bin子目录中(MyClock\bin)。

二、 如何通过HTTP通道传递二进制格式数据

使用IIS的一个缺点是只能使用HTTP通道。HTTP通道将调用封装成SOAP消息,这样会增加消息的长度。IIS只支持HTTP通道,但它并不要求使用将通道调用封装成SOAP消息。默认情况下,HTTP使用SOAP,因为它使用SoapClientFormatterSinkProvide和 SoapServerFormatterSinkProvider来作为序列化和反序列化消息的格式。可以使用BinaryClientFormatterSinkProvider和 BinaryServerFormatterSinkProvder来替换它们。二进制消息可以跟好的利用网络带宽。

下面的Web.config文件注册了一个可以被IIS激活的Clock,它使用二进制替换了缺省的SOAP格式。

<configuration> 
            <system.runtime.remoting> 
            <application> 
            <service> 
            <wellknown mode="SingleCall" type="Clock, ClockServer" 
            objectUri="Clock.rem" /> 
            </service> 
            <channels> 
            <channel ref="http server"> 
            <serverProviders> 
            <formatter ref="binary" /> 
            </serverProviders> 
            </channel> 
            </channels> 
            </application> 
            </system.runtime.remoting> 
            </configuration>


客户端写法如下:

HttpClientChannel channel = new HttpClientChannel 
            ("HttpBinary", new BinaryClientFormatterSinkProvider ()); 
            ChannelServices.RegisterChannel (channel); 
            RemotingConfiguration.RegisterWellKnownClientType 
            (typeof (Clock), "http://localhost/MyClock/Clock.rem"); 
            Clock clock = new Clock ();


当使用配置文件时,写法为:

RemotingConfiguration.Configure ("Client.exe.config"); 
Clock clock = new Clock ();


配置文件内容如下:

<configuration> 
            <system.runtime.remoting> 
            <application> 
            <client> 
            <wellknown type="Clock, ClockServer" 
            url="http://localhost/MyClock/Clock.rem" /> 
            </client> 
            <channels> 
            <channel ref="http client"> 
            <clientProviders> 
            <formatter ref="binary" /> 
            </clientProviders> 
            </channel> 
            </channels> 
            </application> 
            </system.runtime.remoting> 
            </configuration>



使用相同的方法,你也可以在TCP通道中使用SOAP格式封装消息。你甚至可以将自己的格式化方法插入到现有的通道中。

三、 如何使用事件和代理

假设你创建了一个Clock 类,包括一个NewHour事件,代码如下:

public delegate void NewHourHandler (int hour); 
public class Clock : MarshalByRefObject { 
public event NewHourHandler NewHour; 
... 
}

xiaoti 发布于2007-10-16 15:02:11

在IIS上使用的Web.config文件如下:

<configuration> 
            <system.runtime.remoting> 
            <application> 
            <service> 
            <wellknown mode="Singleton" type="Clock, ClockServer" 
            objectUri="Clock.rem" /> 
            </service> 
            <channels> 
            <channel ref="http" /> 
            </channels> 
            </application> 
            </system.runtime.remoting> 
            </configuration> 
            注意ref属性,http值使得two-way HttpCannel代替了one-way HttpServerChannel。



客户端写法如下:

RemotingConfiguration.Configure ("Client.exe.config"); 
            Clock clock = new Clock (); 
            clock.NewHour += new NewHourHandler (OnNewHour); 
            . 
            . 
            . 
            public void OnNewHour (int hour) 
            { 
            // NewHour event received 
            } 
            <configuration> 
            <system.runtime.remoting> 
            <application> 
            <client> 
            <wellknown type="Clock, ClockServer" 
            url="http://localhost/MyClock/Clock.rem" /> 
            </client> 
            <channels> 
            <channel ref="http" port="0" /> 
            </channels> 
            </application> 
            </system.runtime.remoting> 
            </configuration>


客户端也注册了一个two-way HttpChannel,并且指定端口号为0。0值使得通道监听回调,当然也可以允许.NET框架来选择端口数。

四、 如何异步调用一个可远程化对象

缺省情况下,,调用远程对象是同步的。