客户端和服务器怎样检测到另一端是否可用?此时,我们遇到的问题是什么呢?
对于客户端,答案比较简单。只要客户端调用远程对象上的方法,就会产生一个 System.Runtime.Remoting.RemotingException 类型的异常。此时,只需处理这个异常,完成一些必要 的工作,如重试、写日志以及通知用户等。 对于服务器,服务器应何时检测客户端是否还在?即服务器何时可以清理为该客户端保存的资 源?可以一直等待来自客户端的下一个方法调用,但该客户端可能再没有方法调用了。在 COM 领 域中,DCOM 协议使用 ping 机制解决这个问题。客户端把 ping 和引用对象的信息发送给服务器。 因为客户端在服务器上可能有几百个引用的对象,所以 ping 中的信息非常多。为了使这个机制更加 有效,DCOM 不发送所有对象的所有信息,而只发送与上一个 ping 不同的信息。 虽然这个 ping 机制在 LAN 上非常有效,但它并不适用于可伸缩的解决方案。考虑到有成千上 万的客户端向服务器发送 ping 信息,.NET Remoting 为生命周期管理提供了一个伸缩性更强的解决 方案:即租约分布式垃圾收集器(Leasing Distributed Garbage Collector,LDGC)。 这个生命周期管理只对客户端激活的对象和知名的单一对象有效。因为单一对象不保存状态, 所以在每个方法调用之后就可以销毁它们。客户端激活的对象保存状态,我们应该知道它们使用的 资源。如果在应用程序域外部引用客户端激活的对象,就需要创建租约。租约有一个租约时间。当 租约时间为 0时,租约就已经到期,此时远程对象就会断开连接,后由垃圾收集器回收。
1. 租约的续约
当租约到期之后,如果客户端还调用对象上的方法,就会抛出异常。如果有一个客户端,其中 需要租约远程对象的时间超过了 300 秒(默认的租约时间)时,那么有以下 3 种方法进行续约:
● 隐式续约 —— 当客户端调用远程对象上的方法时,租约的隐式续约会自动进行。如果当前 租约时间小于 RenewOnCallTime 的值,租约时间就设置为 RenewOnCallTime。 ● 显式续约 —— 通过显式续约,客户端可以指定新的租约时间,这项工作可以通过 ILease 接口 的Renew()方法完成。通过调用透明代理的 GetLifetimeService()方法,就可以使用 ILease 接口。
● 发起租约 —— 这是第三种续约的方法。客户端可以创建一个实现 ISponsor 接口的发起者, 并使用 ILease 接口的 Register()方法在租约服务中注册这个发起者。发起者定义租约延长的 时间。当租约到期时,发起者就要求延长租约时间。如果要长期租约服务器上的远程对象, 就可以使用这个发起租约机制。
2. 租约的配置值
可以配置下面的一些值:
● LeaseTime ——它定义租约到期之前的时间。
● RenewOnCallTime ——这个时间是租约在方法调用上设置的时间,它指的是续约时间,如 果当前租约时间的值低于这个时间,就要进行续约
● SponsorshipTimeout —— 如果 SponsorshipTimeout 中没有租约发起者,远程基础结构就会 寻找下一个发起者。如果没有更多发起者,租约就到期。
● LeaseManagerPollTime —— 租约管理器隔一段时间就检查一次,查看有没有对象到期, LeaseManagerPollTime 定义这个时间间隔。
3. 管理生命周期所使用的类
ClientSponsor 类实现 ISponsor 接口。在客户端可以使用它延长租约时间。使用 ILease 接口,可以 获取租约的所有信息、所有租约属性,以及当前租约的时间和状态。通过 LeaseState 枚举类型指定状 态。通过 LifetimeServices 实用程序类,可以为应用程序域中所有远程对象的租约设置或获取属性。
4. 获取租约信息示例
在这个小示例代码中,调用透明代理的 GetLifetimeService()方法访问租约信息。对于 ILease 接 口,必须声明 System.Runtime.Remoting.Lifetime 名称空间。对于 UrlAttribute 类,必须导入 System.Runtime.Remoting.Activation 名称空间。 租约机制只能用于有状态的(客户端激活的和单一)对象。由于每次调用方法时都实例化单一调 用对象,因此租约机制不适用于单一调用对象。为了通过服务器提供客户端激活的对象,可以把远 程处理配置更改为调用 RegisterActivatedServiceType()方法:
RemotingConfiguration.ApplicationName = "Hello";
RemotingConfiguration.RegisterActivatedServiceType(typeof(Hello));
在客户端应用程序中,远程对象的实例化也必须更改。在此,并不使用 Activator.GetOnject()方 法,而是使用 Activator.CreateInstance()方法调用客户端激活的对象:
ChannelServices.RegisterChannel(new TcpClientChannel(), true); object[] attrs = {new UrlAttribute("tcp://localhost:8086/Hi") }; Hello obj = (Hello)Activator.CreateInstance(typeof(Hello), null, attrs);
为了显示租约时间,可以调用代理对象中的 GetLifetimeService()方法使用返回的 ILease 接口.
使用 System.Runtime.Remoting.Lifetime.LifetimeServices 实用程序类,服务器自身就可以为所有 远程对象更改默认的租约配置:
LifetimeServices.LeaseTime = TimeSpan.FromMinutes(10);
LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(2);
如果希望不同的默认生命周期依赖于远程对象的类型,则可以重写 MarshalByRefObject 基类的 InitializeLifetimeService()方法,更改远程对象的租约配置:
public class Hello : System.MarshalByRefObject {
public override Object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
lease.InitialLeaseTime = TimeSpan.FromMinutes(10);
lease.RenewOnCallTime = TimeSpan.FromSeconds(40);
return lease;
}
使用后面讨论的配置文件,也可以完成生命周期服务的配置。