前言:这里所谓的高级篇,是针对自己而言,同时也与之前的文章《Microsoft .Net Remoting[基础篇] 》相对应。可能我要描述的内容其实很简单,也是很基础的知识。因为Remoting的内容很多,不知以自己的能力能否写个系列?
一、远程对象的激活
在Remoting中有三种激活方式,一般的实现是通过RemotingServices类的静态方法来完成。工作过程事实上是将该远程对象注册到通道中。由于Remoting没有提供与之对应的Unregister方法来注销远程对象,所以如果需要注册/注销指定对象,微软推荐使用Marshal(一般译为编组)和Disconnect配对使用。在《Microsoft .Net Remoting[基础篇] 》中我已经谈到:Marshal()方法是将MarshalByRefObject类对象转化为ObjRef类对象,这个对象是存储生成代理以与远程对象通讯所需的所有相关信息。这样就可以将该实例序列化以便在应用程序域之间以及通过网络进行传输,客户端就可以调用了。而Disconnect()方法则将具体的实例对象从通道中断开。
根据上述说明,Marshal()方法对远程对象以引用方式进行编组(Marshal-by-Reference,MBR),并将对象的代理信息放到通道中。客户端可以通过Activator.GetObject()来获取。如果用户要注销该对象,则通过调用Disconnect()方法。那么这种方式对于编组的远程对象是否存在生命周期的管理呢?这就是本文所要描述的问题。
二、生命周期
在CLR中,框架提供了GC(垃圾回收器)来管理内存中对象的生命周期。同样的,.Net Remoting使用了一种分布式垃圾回收,基于租用的形式来管理远程对象的生命周期。
早期的DCOM对于对象生命周期的管理是通过ping和引用计数来确定对象何时应当作为垃圾回收。然而ping引起的网络流量对分布式应用程序的性能是一种痛苦的负担,它大大地影响了分布式处理的整体性能。.Net Remoting在每个应用程序域中都引入一个租用管理器,为每个服务器端的SingleTon,或每个客户端激活的远程对象保存着对租用对象的引用。(说明:对于服务器端激活的SingleCall方式,由于它是无状态的,对于每个激活的远程对象,都由CLR的GC来自动回收,因此对于SingleCall模式激活的远程对象,不存在生命周期的管理。)
1、租用
租用是个封装了TimeSpan值的对象,用以管理远程对象的生存期。在.Net Remoting中提供了定义租用功能的ILease接口。当Remoting通过SingleTon模式或客户端激活模式来激活远程对象时,租用对象调用从System.MarshalByRefObject继承的InitializeLifetimeService方法,向对象请求租用。
ILease接口定义了有关生命周期的属性,均为TimeSpan值。如下:
InitialLeaseTime:初始化有效时间,默认值为300秒,如果为0,表示永不过期;
RenewOnCallTime:调用远程对象一个方法时的租用更新时间,默认值为120秒;
SponsorshipTimeout:超时值,通知Sponsor(发起人)租用过期后,Remoting会等待的时间,默认值为120秒;
CurrentLeaseTime:当前租用时间,首次获得租用时,为InitializeLeaseTime的值。
Remoting的远程对象因为继承了MarshalByRefObject,因此默认继承了InitializeLifetimeService方法,那么租用的相关属性为默认值。如果要改变这些设置,可以在远程对象中重写该方法。例如:
public override object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
if (lease.CurrentState == LeaseState.Initial)
{
lease.InitialLeaseTime = TimeSpan.FromMinutes(1);
lease.RenewOnCallTime = TimeSpan.FromSeconds(20);
}
return lease;
}
也可以忽略该方法,将对象的租用周期改变为无限:
public override object InitializeLifetimeService()
{
return null;
}
2、租用管理器
如果是前面所说的租用主要是应用在每个具体的远程对象上,那么租用管理器是服务器端专门用来管理远程对象生命周期的管理器,它维持着一个System.Hashtable成员,将租用映射为System.DateTime实例表示每个租用何时应过期。Remoting采用轮询的方式以一定的时间唤醒租用管理器,检查每个租用是否过期。默认为每10秒钟唤醒一次。轮询的间隔可以配置,如将轮询间隔设置为5分钟:
LifetimeService.LeaseManagerPollTime = System.TimeSpan.FromMinutes(5);
我们还可以在租用管理器中设置远程对象租用的属性,如改变远程对象的初始有效时间为永久有效:
LifetimeServices.LeaseTime = TimeSpan.Zero;
我们也可以通过配置文件来设置生命周期,如:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application name = "SimpleServer">
<lifetime leaseTime = "0" sponsorshipTimeOut = "1M"
renewOnCallTime = "1M" pollTime = "30S"/>
</application>
</system.runtime.remoting>
</configuration>
注:配置文件中的pollTime即为上面所说的租用管理器的轮询间隔时间LeaseManagerPollTime。
租用管理器对于生命周期的设置是针对服务器上所有的远程对象。当我们通过配置文件或租用管理器设置租用的属性时,所有远程对象的生命周期都遵循该设置,除非我们对于指定的远程对象通过重写InitializeLifetimeService方法,改变了相关配置。也就是说,远程对象的租用配置优先级高于服务器端配置。
3、发起人(Sponsor)
发起人是针对客户端而言的。远程对象就是发起人要租用的对象,发起人可以与服务器端签订租约,约定租用时间。一旦到期后,发起人还可以续租,就像现实生活中租方的契约,房东、租房者之间的关系一样。
在.Net Framework中的System.Runtime.Remoting.Lifetime命名空间中定义了ClientSponsor类,该类继承了System.MarshalByRefObject,并实现了ISponsor接口。ClientSponsor类的属性和方法,可以参考MSDN。
客户端要使用发起人机制,必须创建ClientSponsor类的一个实例。然后调用相关方法如Register()或Renewal()方法来注册远程对象或延长生命周期。如:
RemotingObject obj = new RemotingObject();
ClientSponsor sponsor = new ClientSponsor();
sponsor.RenewalTime = TimeSpan.FromMinutes(2);
sponsor.Register(obj);
续租时间也可以在ClientSponsor的构造函数中直接设置,如:
ClientSponsor sponsor = new ClientSponsor(TimeSpan.FromMinutes(2));
sponsor.Register(obj);
我们也可以自己编写Sponsor来管理发起人机制,这个类必须继承ClientSponsor并实现ISponsor接口。
三、跟踪服务
如前所述,我们要判断通过Marshal编组远程对象是否存在生命周期的管理。在Remoting中,可以通过跟踪服务程序来监视MBR对象的编组进程。
我们可以创建一个简单的跟踪处理程序,该程序实现接口ITrackingHandler。接口ITrackingHandler定义了3个方法,MarshalObject、UnmarshalObject和DisconnectedObject。当远程对象被编组、解组和断开连接时,就会调用相应的方法。下面是该跟踪处理类的代码:
public class MyTracking:ITrackingHandler
{
public MyTracking()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
public void MarshaledObject(object obj,ObjRef or)
{
Console.WriteLine();
Console.WriteLine("对象" + obj.Tostring() + " is marshaled at " + DateTime.Now.ToShortTimeString());
}
public void UnmarshaledObject(object obj,ObjRef or)
{
Console.WriteLine();
Console.WriteLine("对象" + obj.Tostring() + " is unmarshaled at " + DateTime.Now.ToShortTimeString());
}
public void DisconnectedObject(object obj)
{
Console.WriteLine(obj.ToString() + " is disconnected at " + DateTime.Now.ToShortTimeString());
}
}
然后再服务器端创建该跟踪处理类的实例,并注册跟踪服务:
TrackingServices.RegisterTrackingHandler(new MyTracking());
四、测试
1、建立两个远程对象,并重写InitializeLifetimeService方法:
对象一:AppService1
初始生命周期:1分钟
public class AppService1:MarshalByRefObject
{
public void PrintString(string contents)
{
Console.WriteLine(contents);
}
public override object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
if (lease.CurrentState == LeaseState.Initial)
{
lease.InitialLeaseTime = TimeSpan.FromMinutes(1);
lease.RenewOnCallTime = TimeSpan.FromSeconds(20);
}
return lease;
}
}
对象二:AppService2
初始生命周期:3分钟
public class AppService2:MarshalByRefObject
{
public void PrintString(string contents)
{
Console.WriteLine(contents);
}
public override object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
if (lease.CurrentState == LeaseState.Initial)
{
lease.InitialLeaseTime = TimeSpan.FromMinutes(3);
lease.RenewOnCallTime = TimeSpan.FromSeconds(40);
}
return lease;
}
}
为简便起见,两个对象的方法都一样。
2、服务器端
(1) 首先建立如上的监控处理类;
(2) 注册通道:
TcpChannel channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel);
(3) 设置租用管理器的初始租用时间为无限:
LifetimeServices.LeaseTime = TimeSpan.Zero;
(4) 创建该跟踪处理类的实例,并注册跟踪服务:
TrackingServices.RegisterTrackingHandler(new MyTracking());
(5) 编组两个远程对象:
ServerAS.AppService1 service1 = new ServerAS1.AppService1();
ObjRef objRef1 = RemotingServices.Marshal((MarshalByRefObject)service1,"AppService1");
ServerAS.AppService2 service2 = new ServerAS1.AppService2();
ObjRef objRef2 = RemotingServices.Marshal((MarshalByRefObject)service2,"AppService2");
(6) 使服务器端保持运行:
Console.WriteLine("Remoting服务启动,按<Enter>退出...");
Console.ReadLine();
3、客户端
通过Activator.GetObject()获得两个远程对象,并调用其方法PrintString。代码略。
4、运行测试:
运行服务器端和客户端,由于监控程序将监视远程对象的编组进程,因此在运行开始,就会显示远程对象已经被Marshal:
<图一>
注:现在博客园似乎有问题,我根本没办法帖图,等正常后再将图补上。
然后再客户端调用这两个远程对象的PrintString方法,服务器端接受字符串:
<图二>
一分钟后,远程对象一自动被Disconnect:
<图三>
此时客户端如要调用远程对象一,会抛出RemotingException异常;
又一分钟后,远程对象二爷被Disconnect了:
<图四>
用户还可以根据这个代码测试RenewOnCallTime的时间是否正确。另外,如果这两个远程对象没有重写InitializeLifetimeService方法,则生命周期应为租用管理器所设,为永久有效(设置为0)。则该两个对象不会被自动Disconnect,除非我们显式指定关闭它的连接。当然,如果我们显式关闭连接,跟踪程序仍然会监视到它的变化,然后显示出来。