我所理解的Remoting

我所理解的Remoting(1):Marshaling & Activation - Part I

什么是Marshaling &Activation

对任何一项分布式技术(Distributed Technology),比如Remoting,XML Web Service,Enterprise Service,Marshaling和Activation(对于Marshaling,我实在是找不到一个比较贴切的中文短语来翻译,很多书把它翻译成封送,我总觉得很别扭,所以在这里我就直接用英文Marshaling,如果读者有较好的翻译,麻烦通知我一下)都是必须要解决的问题。本Blog主要讲述的是在Remoting中的Marshaling和Activation。

首先我们来讲讲到底什么是Marshaling和Activation。我想对于这个问题每个都心中都有自己的定义。我是这样理解的:对于一个对象,当他创建的时候被绑定到(Be Bound)到某一个Context上——这个Context可能是一个内部网络,一台主机,一个进程,一个托管的Application Domain。当另一个Context要调用这个对象,有时候必须 对这个对象作出一些不要的转变(Transformation),这个转变的过程被称为Marshaling。我们一般由两种方式的Marshaling——By Reference 和By Value。前者向是把对象的一个引用传递出去,而后者则是从新创建一个和对象一样的Copy向外传递。

对于任何一个分布式应用来说,Client和Service都分别出于各自的Context之中,Client 以某种方式调用Server(可能是基于Message, 也可能基于RPC)Server),这个调用请求通过一个已经注册到Server端注册的Channel传递到Server端,Server从这个调用请求中提取所需的Metadata信息,创建相应的对象——对Client来说这个对象是一个远程对象(Remote Object)而Activation就是如何创建Remote Object得过程。

Hosting

一个Remote Object能够被不同的Client调用,首先它必须Host在某一个进程之中,对于Remoting来说,你可以选择有很多种选择方式——你可以选择任何一种Managed Application来Host你所需要的Remote Object——Console Application,Windows From Application,ASP.NET Application,一致于Windows Service 中,我们把这种Host方式Self-Host。你也可以把它Host到IIS (6.0 &7.0)以致WAS(Windows Activation Service)。

这个Host的过程本质上包括下面两个方面:

Channel Registration:Channel是Client调用Server的通道,Client调用某个Remote Object,这个调用请求首先转化成一个Message,这个Message通过Client选择的一个Channel从Client AppDomain传递到Server Appdomain,同理执行执行的结果(Result)也以同样的方式从Server Appdomain传递到Client AppDomain。这里有一个根本的前提,Client选择的Channel必须先在Host中注册过。

Object Registration:Object Registration的本质就是把Remote Object相关的原数据(Metadata)注册到Host环境中,并为它制定一个Objet URI。如果说Channel Registration结果了如何Communication的问题,Object Registration可以看成是解决如何创建Remote Object和验证调用的合法有效性问题——它利用MetaData来创建Remote Object和验证Client端的调用请求。MSDN把这个过程称为Object Registration,我实际上不太赞成这种说法,因为这个过程做的仅仅是注册Remote Object Metadata的信息——实际上就是Type的信息,中间不曾有过对象的创建。所以我觉得叫做Remote Type Registration更加准确点。

当完成Object Registration之后,Remoting Framework根据注册信息找到Server对应的Assembly,从而提取出所需要的Metadata,结合注册的Object的Uri 、Assembly Name、Channel相关的信息,创建一个类型为ObjRef的对象,这个对象基本上包含了能够调用对应Remote Object的所有信息(关于ObjRef,下面的章节会后介绍,如果想查看详细的信息,你可以参考MSDN)。Remoting Framework内部维护着一个Table用于存储他所有注册的类型。

Proxy

在托管的环境(Managed Environment)下,Application Domain把一同的每个Managed Application隔离在它们各自的区域内,在一个Application创建的对象不能被另一个Application所直接调用。他必须通过Marshaling以传递引用或者传递从一个Application Domain传递到另一个Application Domain中。关于Application Domain的隔离性可以参照我的文章(.NET Framework——用Coding证明Application Domain的隔离性 )。

对于Remoting来说,Remote Type继承的是System.MarshalByRefObject,从名称就可以看出,它是以传递Reference的方式来Marshal 的。为了使我们更加准确地理解MarshalByRefObject,我们需要引入一个新的类System.Runtime.Remoting.ObjRef。ObjRef是一个可序列化的对象,用于扩展MarshalByRefObject对象(MRB Object)。当一个MRB Object被Marshal的时候,实际上Remoting Framework会根据MRB Object创建一个ObjRef,这个ObjRef包含了Client调用此Remote Object的一切信息——Remote Object对应的Type信息;Remote Object实现的Interface;调用这个Remote Object所用的Channels;以及Remote Object所在的Uri。由于ObjRef是可序列化的,所以他以传值的形式传递到Client Application Domain ——Remote Object以Marshal By Reference的形式通过ObjRef实现的Application Domain之间的传递,而ObjRef本身则是以Marshal By Value的形式传递的。当此ObjRef到达Client Application Domain后,会在Client端创建一个Proxy,通过这个Proxy便可以远程地调用Remote Object了。

接下来我们结合图来了解具体的调用过程:
 

 


在Client Application,一个Client Object调用一个Transparent Proxy,这个Transparent Proxy把这个调用转换成一个IMessage对象,并把这个IMessage对象传递给RealProxy 对象,RealProxy调用Invoke方法并把该IMessage对象传递给Invoke方法。RealProxy调用CreateObjRef方法得到Remote Object的ObjRef,并通过Client注册的Channel把这个调用传递到Server Application Domain。

Activation

.NET Remoting有两种不同的Activation方式——Server Activation 和Client Activation。

Server Activation:客户端一般通过Activator的静态方法GetObject方法在Client端创建Transparent Proxy 和Real Proxy,Transparent Proxy被最终传道给Client。这里有非常重要的一点,通过上面的分析,我们知道,Proxy的建立需要Remote Object的ObjRef,而此时这个ObjRef处在Server端的AppDomain中,在创建Proxy的时候,Client是否会为了获取Remote Object ObjRef 而进行网络连接呢?答案是否定的,在Client端创建Proxy的时候,是不会使用任何对于Server的网络访问的。而创建Proxy所必需的是在Client端获取的——我们可以用编程和配置信息的方式为他指定Remote Object Metadata的信息(我们可以传入Remote Object Type 或者Remote Object Type实现的Interface)和Remote Object的Uri。而通过在Client端创建的ObjRef和通过网络访问从Server端获取的具有相同的功效。

当Client的Transparent Proxy创建了以后,这个Transparent Proxy就成了Remote Object 在Client端的代理。我们调用Transparent Proxy的某一个方法,Transparent Proxy会先把这个调用转化成一个Message(实现了IMessage Interface);然后调用Real Proxy (默认的是一个System.Runtime.Remoting.Proxies.RemotingProxy对象)的Invoke方法,同时把Message传入该方法。Real Proxy 调用GetObjRef方法获得Remote Object Metadata的信息来验证这个请求,验证失败,抛出异常。然后Real Proxy判断调用的对象是存在于和自己相同的AppDomain(MRB 不单单是用在跨AppDomain调用的场景),如果是直接在本地创建一个对象,执行相应的操作,把执行结果通过Transparent Proxy返回给Client。如果判断结果表明是一个远程调用,则通过ChannelInfo属性,获取Channel的信息,最终把Message通过Channel传递到Server端——这中间回经历序列化和编码等操作。

前面我们讲过,当Remote Obejct被Host的时候,Remoting Framework 会创建一个内部的Table用于记录他所有被注册的Remote Object Type。一旦完成了Host,Server端便开始利用所注册的Channel进行监听。一旦他监听到某一格来自Client端的请求,他把Message 截获下来,获取Remote  Object(对于Client来说)的ObjRef,并同这个内部表的Items进行比较,从而知道需要激活的对象。如果Mode 是SingleCall创建一个对象,执行相应的操作,返回结构后销毁该对象。如果是Singleton模式,会判断相应的对象是否存在(这个说法不太准确,应该说是否有相应的对象存在,并没有标记为过期——具体的原因,可以留意我的下一篇Blog——关于Remoting的Lifetime  Management),如果存在则直接调用该对象,如果不存在则重新创建,然后执行相应的操作。对于Singleton模的下的对象,其生命周期通过LifetimeManager来控制,这是一个机遇Lease的控制策略。

Client Activation:前面我们花了大量的篇幅来解释Server Activation,其中一个主要的特点是,Remote Object在第一次调用时激活,而不是在Client创建Proxy的时候激活。正因如此,对于一个Server Activated Object来说,它的对象的创建之只能通过默认的无参构造函数来创建。任何有参构造函数的定义没有任何意义,并且没有定义无参构造函数在调用会抛出异常。

相对于Server Activation,Client Activation采用了完全不同的激活方式。在Client Activation方式下,当Client调用New或者Actiovator.CreateInstance方法(再这之前,Client必须在Client端注册Channel和Remote Object Type),Remoting Framework会在Client段创建一个Activation Proxy,这个Activation Proxy远程地调用Server端的一个Activator远程对象,这个Activator对象激活相应的对象,创建相应的ObjRef传递到Client端,Client端利用这个ObjRef创建Real Proxy 和Transparent Proxy。至此Client端就可以通过该Transparent Proxy进行远程调用了。

从这个过程中我们可以看到,Remote Object实在Client创建Proxy的时候同时创建的,所以创建Proxy时指定的信息可以 传递到Server端,所以对于Client Activated Object,他们是可以由自定义参数的构造函数的。

你可以通过以下的Link获得一个全面的Sample([原创]我所理解的Remoting(1):Marshaling & Activation - Part I I  

我所理解的Remoting(1):Marshaling & Activation - Part II

 在上面一片文章([原创]我所理解的Remoting(1):Marshaling & Activation - Part I ),我花了大量的文字来来描述了Remote Object如何通过Marshaling的过程从Server端所在的Application Domain经过相关的转换(Transformation)传递到Client所在的Application Domain供Client调用; 以及Client的调用请求如何在Activate处于Server端Application Domain的Remote Object。大体的要点如下:

Host在Server端注册Client可能会用到的一到多个Channel用于传输Client发出的调用请求(这个调用请求最终被序列化成一Message)——Channel Registration。然后把Remote Object的相关Metadata信息和remote Object Uri(Remote Object Type的信息)注册到Host进程中——Object Registration。完成了Object Registration之后,Remoting Framework分析注册的信息Load相应的Assembly,利用Reflection的机制为相应的Remote Type生成一个可序列化ObjRef(可序列化以为着ObjRef对象可以穿梭Application Domain),并将它保存到一个Internal Table 之中(这个Internal Table用于Track Remote Object)。

Remoting有两种Activation 方式——Server Activation 和Client Activation。而Server Activation有具有两种不同的Mode——SingCall和Singleton(SingleCall和Singleton严格地说是关于Instance Management的概念——这个概念在WCF中被引入)。对于Server Activation,Client端根据注册在Client端的Remote Object的Metadata创建Real Proxy和Transparent Proxy。在创建Proxy的时候,不曾有任何访问请求发送到Server端,与此同时,也不可能有相应的Remote Object在Server 端被创建。而真正第一次网络访问发生在第一次通过Transparent Proxy调用某个方法。当这个方法请求从某个注册在Server段的某个Channel抵达Server端的时候,Server 端的Remoting Framework提取请求Message 的Remote Object 的ObjRef,同上面提到的Internal Table的相关Entry进行比较,获得所需的Metadata信息,通过Reflection创建Remote Object。这就是Server Activation的简单过程。

Client Activation采用了不同的Activation 方式——Client端的Proxy(Both Transparent Proxy和Real Proxy )和Remote Object几乎在同时创建(当然在不考虑远程调用时延的因素)。当Client通过New或者Activator.CreateInstance在Client创建Proxy的时候实际上是经历了以下一个过程:一个Activator Proxy首先在Client端创建,借助这个Activator Proxy Remoting Framework发送一个Activation请求到Server端,Server端的Remoting Framework根据Activation Request的Metadata信息和已经注册的Remote Type做一个匹配,提取所需的Metadata通过Reflection的机制创建Remote Object。同时创建这个Remote Object的ObjRef并通过相应的ChannelForward到Client端,随之生成RealProxy 和Transparent Proxy,Transparent Proxy被Client调用。

上面基本上就是我在上一篇Blog的中心。可能看过我Blog的人都知道,我几乎在每篇文章中都会有一个Sample,我不太相信流于文字的理论,我喜欢用实践来证明。所以下面我们将用Sample来证明。这个Sample中将沿用简单的Calculator的应用。Source Code可以从这里下载(Artech.MyRemoting.zip

1. 整个Solution的结构。

 

 

这个结构其实是我比较鄙视的分布式结构——Client端和Server通过Share一个Type System来共享整个Service(包括Interface和Implementation ,在WCF中我们这两部分称为Service Contract和Service Implementation)。但是由于Client Activated Object在创建Proxy的时候需要制定MarshalByRefObject的Type,所以我们不得不用这种结构——Client和Server共享定义在Artech.MyRemoting.RemoteService中定义的MarshalByRefObject Class:CalculatorService。大家可以可以看到Artech.MyRemoting.Hosting和Artech.MyRemoting.Client都有Artech.MyRemoting.RemoteService的Reference。话又说回来,我们可以应用某些策略使只把Service的Interface公开给Client——即使是SAO对象,相关的内容超出了这篇文章范畴,我会相关的讨论放到后续的Remoting相关的Blog中,如有兴趣可以留意。

2. 在Artech.MyRemoting.RemotingService 定义我们的Service——尽管Remoting算不上是完全基于SOA的Distributed Technology,但我还是会不自觉地使用SOA相关的术语,不当的之处,还往见谅。

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;

namespace Artech.MyRemoting.RemoteService
{
    
public class CalculatorService:MarshalByRefObject
    
{
        
private int _callCount;

        
public CalculatorService()
        
{
            Console.WriteLine(
"Remote object is activated at {0}\n", DateTime.Now.ToString("hh:mm:ss"));
        }


        
public double Add(double x, double y)
        
{
            
this._callCount++;
            
return x + y;
        }


        
public int GetCallCount()
        
{
            
return this._callCount;
        }

    }

}

Code 很简单——一个Constructor用于确定Remote Object到底在什么时候创建或者说的专业一点,真正的Remote Object什么时候被Activated。一个GetCallCount用所Add方法调用次数的计数器,这个计数器用来验证Remoting 的Instance Management。和一个执行加法运算的方法,注意执行一次我们的保存调用次数的变量就是加1。

3. Host CalculatorService

App.Config

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<system.runtime.remoting>
    
<application name="Artech.MyRemoting">
      
<service>
        
<wellknown type="Artech.MyRemoting.RemoteService.CalculatorService,Artech.MyRemoting.RemoteService"
                   mode 
="SingleCall" objectUri="SingleCall.Calculator.rem"></wellknown>
        
<wellknown type="Artech.MyRemoting.RemoteService.CalculatorService,Artech.MyRemoting.RemoteService"
                 mode 
="Singleton" objectUri="Singleton.Calculator.rem"></wellknown>
        
<activated type="Artech.MyRemoting.RemoteService.CalculatorService,Artech.MyRemoting.RemoteService"></activated>
      
</service>
      
<channels>
        
<channel type="System.Runtime.Remoting.Channels.Http.HttpChannel,System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                 port
="1234"></channel>
      
</channels>
    
</application>
  
</system.runtime.remoting>
</configuration>


从这个config文件可以看到,我们为同一个CalculatorService以不同的方式注册了3此——SingleCall,Singleton和CAO。

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;

namespace Artech.MyRemoting.Hosting
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            RemotingConfiguration.Configure(
"Artech.MyRemoting.Hosting.exe.config"false);
            Console.WriteLine(
"The Calculator services have begun to listen ");

            Console.Read();
        }

    }

}

很简单,无需赘言。

4. 编写客户端

 

using System;
using System.Collections.Generic;
using System.Text;
using Artech.MyRemoting.RemoteService;
using System.Runtime.Remoting;
using System.Threading;

namespace Artech.MyRemoting.Client
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            
string singelCallAddress = "http://localhost:1234/artech.myremoting/singlecall.calculator.rem";
            
string singletonAddress = "http://localhost:1234/artech.myremoting/singleton.calculator.rem";
            
string caoAddress = "http://localhost:1234/artech.myremoting";

            RemotingConfiguration.RegisterActivatedClientType(
typeof(CalculatorService), caoAddress);

            Console.WriteLine(
"Create server SingleCall proxy at {0}", DateTime.Now.ToString("hh:mm:ss"));
            CalculatorService singleCallCalculator 
= Activator.GetObject(typeof(CalculatorService), singelCallAddress) as CalculatorService;

            Thread.Sleep(
10000);
            Console.WriteLine(
"Create server Singleton proxy at {0}", DateTime.Now.ToString("hh:mm:ss"));
            CalculatorService singletonCalculator 
= Activator.GetObject(typeof(CalculatorService), singletonAddress) as CalculatorService;

            Thread.Sleep(
10000);
            Console.WriteLine(
"\nCreate client activated object proxy at {0}", DateTime.Now.ToString("hh:mm:ss"));
            CalculatorService caoCalculator 
= new CalculatorService();

            Thread.Sleep(
10000);
            Console.WriteLine(
"\nCall the method of SingleCall object at {0}", DateTime.Now.ToString("hh:mm:ss"));
            InvocateCalculator(singleCallCalculator);

            Thread.Sleep(
10000);
            Console.WriteLine(
"\nCall the method of Singleton object at {0}", DateTime.Now.ToString("hh:mm:ss"));
            InvocateCalculator(singletonCalculator);

            Thread.Sleep(
10000);
            Console.WriteLine(
"\nCall the method of CAO object at {0}", DateTime.Now.ToString("hh:mm:ss"));
            InvocateCalculator(caoCalculator);

           
            Console.WriteLine(
"The times to invovate the current SingleCall remote object is {0}", singleCallCalculator.GetCallCount());
            Console.WriteLine(
"The times to invovate the current Singleton remote object is {0}", singletonCalculator.GetCallCount());
            Console.WriteLine(
"The times to invovate the current CAO remote object is {0}", caoCalculator.GetCallCount());

            Console.Read();
        }


        
static void InvocateCalculator(CalculatorService calculator)
        

            Console.WriteLine(
"x + y = {2} where x = {0} and y = {1}",1,2,calculator.Add(1,2));
        }

    }

}


这里有必要多说几句:

我们首先创建基于3种不模式的Proxy,为了弄清楚整个Activation的流程,对于每个操作都为它显示出具体的执行时间,通过操作的执行会间隔一段时间(我给它指定的是10s)

 

string singelCallAddress = "http://localhost:1234/artech.myremoting/singlecall.calculator.rem";
            
string singletonAddress = "http://localhost:1234/artech.myremoting/singleton.calculator.rem";
            
string caoAddress = "http://localhost:1234/artech.myremoting";

            RemotingConfiguration.RegisterActivatedClientType(
typeof(CalculatorService), caoAddress);

            Console.WriteLine(
"Create server SingleCall proxy at {0}", DateTime.Now.ToString("hh:mm:ss"));
            CalculatorService singleCallCalculator 
= Activator.GetObject(typeof(CalculatorService), singelCallAddress) as CalculatorService;

            Thread.Sleep(
10000);
            Console.WriteLine(
"Create server Singleton proxy at {0}", DateTime.Now.ToString("hh:mm:ss"));
            CalculatorService singletonCalculator 
= Activator.GetObject(typeof(CalculatorService), singletonAddress) as CalculatorService;

            Thread.Sleep(
10000);
            Console.WriteLine(
"\nCreate client activated object proxy at {0}", DateTime.Now.ToString("hh:mm:ss"));
            CalculatorService caoCalculator 
= new CalculatorService();

依次调用三个Proxy的Add方法显示调用的准确时间

Thread.Sleep(10000);
            Console.WriteLine(
"\nCall the method of SingleCall object at {0}", DateTime.Now.ToString("hh:mm:ss"));
            InvocateCalculator(singleCallCalculator);

            Thread.Sleep(
10000);
            Console.WriteLine(
"\nCall the method of Singleton object at {0}", DateTime.Now.ToString("hh:mm:ss"));
            InvocateCalculator(singletonCalculator);

            Thread.Sleep(
10000);
            Console.WriteLine(
"\nCall the method of CAO object at {0}", DateTime.Now.ToString("hh:mm:ss"));
            InvocateCalculator(caoCalculator);

获得他们的计数器,看看调用次数有何不同。
 

 

Console.WriteLine("The times to invovate the current SingleCall remote object is {0}", singleCallCalculator.GetCallCount());
            Console.WriteLine(
"The times to invovate the current Singleton remote object is {0}", singletonCalculator.GetCallCount());
            Console.WriteLine(
"The times to invovate the current CAO remote object is {0}", caoCalculator.GetCallCount());

现在我们首先启动Hosting,输出表明Server端正常监听。


 

 

启动Client,产生如下的输出:
 

 



然后我们再看看现在Hosting的输出:
 

 



我们现在来分析一下为什么会有如此输出结果:

  • 我们在04:40:02和04:40:12创建了一个SingleCall Proxy,Server端没有反应,这就充分验证了对于Server Activation来说,在Client端创建Proxy的时候,并没有Remote Object在Server端被Activated。
  • 接着我们在04:40:22创建一个CAO Proxy,Remote Object 对象在一分钟后便被Activated。表明Proxy和Remote Object 几乎是同时创建的。
  • 10s后,我们分别调用SingleCall和Singleton Proxy的方法,Server端也在同一时间给出反应,这就说明了,对于Server Activation 来说,第一次调用才会导致Remote Object的Activation。
  • 随后,我们在04:40:53调用CAO Proxy的方法,Server端没有任何输出,因为这个Proxy对应的Remote在Proxy创建的时候就已经被创建了。(Server端的最后一行输出实际上是调用SingleCall Proxy的GetCallCount方法时输出的——对于SingleCall来说,对于Proxy的每次调用都会创建一个Remote Object,调用完毕被Garbage Collected。看来这个Sample 还是不够好,不过相信大家能够理解了)。
  • 接下来,我们分别获取3个对象的调用计数 。对于SingleCalll Proxy 来说,每次调用都会创建一个新的Remote Object,所以Add方法的调用次数永远为零,而对于Singleton来说,所有的Client共享一个Remote Object, 所以它能保持上次来自任意一个Client调用时的State,对于CAO来说,一个Client和一个Remote Object,所以他能够保持自己的调用的State。所以我们不然想象,当我们在开启一个Client,会有什么样的输出:
     

 


注: 以上的输出都应该基于这样的前提:创建的Remote Object没有被回收。相关的原理相对比较复杂,它将会出现在的后续的关于Remote  Object Lifetime Management的Blog中,有兴趣的网友可以关注。

我所理解的Remoting(2):远程对象生命周期的管理—Part I

1.CLR的垃圾回收机制

在.NET中提到对象的生命周期,我们会不由自主地想到CLR的垃圾回收。在运行一个.NET程序过程中,我们通过某种方式,比如通过new操作符,通过反序列化,通过反射机制,创建一个对象,CLR在为这个对象在托管堆中开辟一块内存空间。随着程序的运行,创建的对象越来越多,托管堆中的可用的内存越来越少,必须有一种机制来判断被分配在托管堆中的对象那些已经不被使用,以及进行对这些对象占用的内存进行回收。这种机制被称为CLR自动内存管理,也就是我们常说的垃圾回收。为了说清楚远程对象的生命周期管理,我们 得首先了解本地对象的生命周期。

首先我们来说说CLR如何判断分配在托管堆中的对象那些是可以被垃圾回收的。我想我们应该可以想象得到,在程序运行的某个时刻,以下一些对象是会在后续的运行中会时候使用到的:一个类的静态字段,一个全局变量,调用方法堆栈中存储的方法参数和创建的局部变量,CPU 寄存器。我们一般把这些对象称为根(root),所有的根和被它直接引用或间接引用的对象,我们都认为是不应该被回收的。在CLR的内部维持着一个特殊的数据结构,这个表的每个条目对应一个跟,当CLR加载的时候,该表被创建。随着程序的不断运行,新的根会被加进来,已经不是跟的会被删除掉,所用这个表可以看作根的实时的反应。当垃圾回收器进行工作的时候(一般是第0代对象所对应的托管堆充满的时候,当然你也可以手动地调用GC.Collect方法启动垃圾回收。),它首先在托管堆上扫描,如果发现该的内存快所对应的对象是一个根,它会在该对象同步快索引(synchronous block index)字段做一个特殊的标记,表明它当前还不可以被垃圾回收,接着他会一递归的方式标记所有 被它直接或者间接引用的对象。所以当扫描完毕之后,那些被标记的对象就是在当前不可以被当成垃圾回收的对象,这些对象一般称为可达对象(reachable object,因为可以通过根来找到),反之,除此以外的对象则就是垃圾对象了。

标记可达对象只是垃圾回收的第一步,第二步才是对这些未被标记的垃圾对象进行回收。在开始之前我们必须能够分别两种不同的对象,一种称为可终结(Finalizable)对象和非可终结对象。我们知道,在很多情况下,我们的对象会引用一些非托管的资源,比如一个文件的句柄,一个数据库连结,或者一个Socket连结等等。在我们回收这些对象的时候,如果没有这些非托管的资源进行相应的终结操作的话,很有可能造成内存的泄漏。在.NET中,我们通常把这些终结操作写在一个特殊的方法中,叫做Finalize。在C#中我们一般定于在~ClassName()的形式,并且沿用C++ 的说法,称它为析构函数(我不推荐这么称呼,Finalize方法和C++的析构函数是不同的)。如果你有兴趣查看C#编译之后生成的IL代码,你会发现定义成~ClassName()的方法的名称就是Finalize。我们把相应的类定义了Finalize方法的对象叫做可终结的对象。说到可终结对象,这里我们又需要引入一个新的垃圾回收器维护的数据结构,这是的链表,用于保存未被回收的可终结对象,一般称为终结链表。

接下来我们看看垃圾回收器如何进行垃圾回收的。垃圾回收器开始再一次扫描托管堆,对于在第一步作了标记的对象,不予理睬。对于未作标记的对象,首先判断是否是可终结对象(看看在终结链表中是否有相应的对象),如果不是,直接回收掉。否则垃圾回收器还要先判断是否已经进行了终结操作,如果没有则会把它从终结链表中移除,把对象放入另一个称为终结可达对列(freachable queue——f代表finalizable,注意这里又引进了一个新的数据结构)。如果已经进行了终结操作,则直接进行回收就好了。

对于放入终结可达对列对象,我们必须在对它进行垃圾回收之前收前进行终结操作。从广义来讲终结可达对列中的对象也是一个根,所以被放入终结可达对列中的对象是不应该被垃圾回收的,由于终结操用涉及到线程同步的问题,所有的终结操作都在一个具有较高优先级的线程上进行。这个线程在终结可达对列为空的时候处于休眠的状态,一旦垃圾回收器把一个可终结对象放入这个终结可达对列的时候,这个特殊的线程立即被唤醒,调用该对象的Finalize方法并把它从终结可达对列中移除。等再一次进行回收的时候,对于这些经过终结操作对象已经成为垃圾对象——不会有任何的根,包括终结可达对列引用它,这个时候垃圾回收器可以对它进行回收了。

从垃圾的整个过程来看,如果我们重写了Finalize方法使之成为一个可终结类型,这种对象实际上要经过两次垃圾回收才会被真正地回收调——其中一次放入终结可达对列,另一次才真正被回收调。所以,我们在定义某个类型的时候,如果没有必要重写Finalize方法就千万不要重写它,不然会加重内存的压力,降低应用的性能。
 



2.基于租约(Lease)的生命周期管理机制

在前面我们简单地讲述了CLR垃圾回收机制。按照这种机制,如果我们要阻止一个对象被垃圾回收,我们必须让它被某个根直接或间接引用,而这个引用它的对象一般应该和该对象处于同一个Application Domain之中。所以这种机制不适合我们分布式环境。在一个分布式应用来说,服务器段对象通过Marshaling从Sever端的Application Domain传到Client所在的Application Domain。无论采用哪种Marshal方式,By Value 或者By Reference,Marshal本身是不会为远程对象在Server端的Application Domain创建一个引用的。如果这样的话,对于垃圾回收器来说,远程对象便成了一个垃圾对象,在进行垃圾回收的时候,必然会被回收掉。如果Client端调用一个已经被回收的远程对象,对于Client Activated Object,会抛出异常,如果对于Singleton模式的Server Activated Object,Remoting Framework会重新创建一个新的对象,原有的状态将不复存在。所以我们必须有一种机制来控制远程对象的生命周期。这就是我们现在讲的基于租约(Lease)的生命周期管理。

在正式讲述之前,我首先我讲一个现实生活中的一个困难不是很恰当的例子,一个租房的例子。

去年9月份,我从苏州来到上海,通过中介租了一间房子,很巧的是这个中介就是我的房东,并且我是和房东合租的。当时觉得不是太满意,所以签合同的时候只签了半年。在租期还没有到期的时候,我有权向房东提出续租,租期可以使半年,也可以使一年。如果到期了,我没有提出续租,房东肯定会主动和我联系,询问我时候有续租的打算,如果我提出续租,考虑到我们房东和和睦相处的关系,我们可以在不需要签订新的合同的情况下让我续租。否则我走人,我的房间被转租出去。如果房东在找我的时候,可能我出差了,手机又换号了,联系不到我,这时候,他肯定会打联系我的女朋友,他们也很熟,如果我的女朋友说要续租,房东便会在没有获得我答复的情况下,给我续租。但是现在租期已经到期了,我也没有提出续租,房东也没有叫我续租,但是我还是每个月给他交房东,虽然合同已经在法律的意义上失效了,但是我和清楚,我交了下个月的房租,我的房间到下个月底使用权归我。这就是我租房的故事(呵呵)。大家注意这样故事中的几个实体,合同,房东兼中介,房间,我(合同上的承租者),我女朋友。



现在我们再来讲Remoting关于Lease的对象生命周期的管理机制。当远程对象被激活和Marshal的时候,处于Server端Application Domain的Lease Manager会为该远程对象创建一个Lease。就相当于中介和我签了一份租房合同,中介相当于Lease Manager,我(承租者)相当于客户端,而房间就是这个远程对象,Lease则代表我们签订的租房合同。就像合同会写明租期一样,Lease也会有一个初始的时间(InitialLeaseTime)代表远程对象的初始生命周期。就像我可以在租期到期之前可以自动提出延长租期一样,Client可以通过这个Lease来延长对应远程对象的生命周期。不过和租房的例子不同的是,Server端也可以具有相同的权利。

就像我可以通过交房租来延长一个月的租期一样,远程对象可以通过来自Client端的调用来延长这个Lease,这个时间由属性RenewOnCallTime来表示。不过有一点值得注意的是,就像我在租期到了的那个月之前交房租这个行为不会延长租期(始终是6个月),只有我在第6个月月底交房租才会把实际的租期延长到7月个。在Remoting中,只有在RenewOnCallTime大于Lease剩下的时间的时候,这个RenewOnCallTime才会起作用。

就像我可以让房东在我不在的时候,找我的女朋友来代表我一样,在Remoting中,Client可注册一个Sponsor,这个Sponsor有权代表Client延长租房期限,当然Client有权利取消这个注册的Sponsor,就像有一天我和女朋友分手了,她就没有这样的权利了。随着时间的推移,当Lease的过期了,Lease Manager会首先通过远程调用(可以把这种情况看成一种Callback),从Client获得Client为相应远程对象注册的Sponsor,找到了之后,通过这个Sponsor设置的时间来延长远程对象的生命周期。但是,我们已经说了,Lease Manager获得Sponsor是一种远程调用,可能他们处在不同的Application Domain,不同的Process,不同的Machine,甚至处于Internet的两端。这个调用是否成功和调用的时间是不确定的,所以这里必须给定一个特定的时间来规定,在某一段限定的时段内,如果不能获得相应的Sponsor则认为该Sponsor不可得。否则始终这个调用下去也不是个事儿。就像房东在房租到期一个月之内还找不到我和我女朋友,关系再好也必须把房间转租出去了。对于Lease来说,这个时间通过SponsorshopTimeout属性表示。

这里还有一个重要的时间,那就是Lease Manager每个多少时间扫描Lease——LeaseManagerPollTime。

上面的所有的时间都是可以定制的,我们现在看看,如何定制这些时间。

1. 通过编程的方式:通过设置System.Runtime.Remoting.Lifetime. LifetimeServices静态属性。

 

LifetimeServices.LeaseManagerPollTime = TimeSpan.FromMinutes(2);
LifetimeServices.LeaseTime 
= TimeSpan.FromMinutes(2);
LifetimeServices.RenewOnCallTime 
= TimeSpan.FromMinutes(2);
LifetimeServices.SponsorshipTimeout 
= TimeSpan.FromMinutes(2);

2. 通过Configuration的方式

 

<lifetime  leaseTime="7M"  sponsorshipTimeout="7M"  renewOnCallTime="7M" 
leaseManagerPollTime
="7S" />

3. 定制单个MarshalByRefObject 对象的Lease 时间:Override 继承自MarshalByRefObject 的InitializeLifetimeService方法。

public override object InitializeLifetimeService()
        
{
            ILease lease 
= (ILease)base.InitializeLifetimeService();
            
if (lease.CurrentState == LeaseState.Initial)
            
{
                lease.InitialLeaseTime 
= TimeSpan.FromSeconds(1);
                lease.RenewOnCallTime 
= TimeSpan.FromSeconds(1);
                lease.SponsorshipTimeout 
= TimeSpan.FromSeconds(1);
            }

            
return lease;           
        }

通过上面的讲述,我的应该对Remoting对象生命基于Lease和Sponsorship的生命周期的管理有一个感性的认识。实际上Sponsorship远非这么简单,对Sponsorship的深入探讨,有兴趣话可以关注:[原创]我所理解的Remoting (2) :远程对象的生命周期管理-Part II .



Reference:Jeffery Richiter 《CLR via C#》 

 

我所理解的Remoting (2) :远程对象的生命周期管理-Part II

上一篇文章中([原创]我所理解的Remoting(2):远程对象生命周期的管理—Part I ),我简要的讲述了CLR的垃圾回收机制和Remoting 基于Lease的对象生命周期的管理。在这篇文章中,我们将以此为基础,继续我们的话题。在文章的开始,我将以我的理解详细地讲述Remoting中两个重要的概念——Lease和Sponsorship。然后我通过一个Sample,为大家演示如何以不同的方法延长远程对象的生命周期。

我们先不谈远程对象、本地对象。 不管是远程的对象,还是本地对象,都对于程序Application Domain 所在地址空间的一块连续的托管堆(managed heap)内存区域。在.NET环境下,其对象的生命周期,也就是对应托管堆的回收,都由垃圾回收器(garbage collector)负责。当需要进行垃圾回收时(如果不是强制进行垃圾回收,和程序卸载,垃圾回收操作一般出现在第0代对象充满的时候),垃圾回收器扫描托管堆,标记垃圾对象(实际上是标记非垃圾对象,未被标记的则是垃圾对象),并最终回收垃圾对象。

通过前面章节的介绍,我们知道了,CLR通过当前程序运行时的一个根(root)的列表来判断一个对象是否是垃圾对象——没有被根直接或者间接引用的对象则是垃圾对象。从另一个角度讲,如果想让一个对象存活,或者你试图让一个对象具有更长的生命周期,那么不就必须使它被一个根直接或者间接引用——比如你可以使用一个全局变量引用它,那么在这个全局变量的生命周期内,这个对象就会一直存活;你甚至可以让一个静态变量引用它,那么这个对象将永远不会被垃圾回收,直到所在的AppDomain被卸载。而我们现在来讨论Remoting 中远程对象生命周期的管理,说白了,其本质就是在Remoting Framework中,如何创建一些具有根性质的对象引用创建的远程对象(相对于Client端来讲),从而适当地(我们不能让远程对象具有太长的生命周期,那样会见中内存的压力,同样我们也不能使远程对象,那样会造成频繁的对象的频繁创建进而影响系统的性能)阻止垃圾回收器回收该对象。

那么这个引用远程对象的对象是谁呢?它就是我们要讲的Lease。当Client端的Proxy通过Marshal传到Host环境的时候,Remoting Framework 激活对应的远程对象。与此同时,Lease Manager(整个机遇Lease生命周期管理的总负责)会为该远程对象创建对应的Lease,从垃圾回收的角度讲,远程对象有了Lease对象的引用,则可以在垃圾收器的铡刀下得以幸存。但是通过前面的分析,远程对象不能老是存活者,他是具有一定的生命周期的,也就是说,一旦到了该寿终正寝的时候,垃圾回收器就该对他动刀子。而且,这个生命周期应该是可以配置的,系统地设计人员根据具体的程序运作状况,计算出一个合理的生命周期,在部署的时候,通过配置文件为之设定。

那么这样的机制又是如何实现的呢?到现在为止我们知道,远程对象存在的唯一条件就是它的Lease存在,Lease一旦垃圾回收了,那么它的死期也不远了。这样我们就可以通过Lease对象的生命周期来间接地控制远程对象的生命周期。而Lease对象的生命周期是可以配置的。那么现在我们可以把我们的关注点放在如果控制Lease的生命周期上来。在Remoting中,Lease是实现System.Runtime.Remoting.Lifetime. ILease的类的对象。

 

namespace System.Runtime.Remoting.Lifetime
{
    
// Summary:
    
//     Defines a lifetime lease object that is used by the remoting lifetime service.
    [ComVisible(true)]
    
public interface ILease
    
{
        LeaseState CurrentState 
get; }
        TimeSpan InitialLeaseTime 
getset; }
        TimeSpan RenewOnCallTime 
getset; }
        TimeSpan SponsorshipTimeout 
getset; }
        
void Register(ISponsor obj);        
        
void Register(ISponsor obj, TimeSpan renewalTime);        
        TimeSpan Renew(TimeSpan renewalTime);        
        
void Unregister(ISponsor obj);
    }

}

了解在托管环境下Lease是怎样一个对象之后,我们来看看,在Remoting中Lease的生命周期是如果决定的。在前面一节我们提到过我们有3种方式来设置Lease 的各个属性(初始的时间:InitialLeaseTime,一个远程调用所能延续的时间:RenewOnCallTime,Lease Manager联系对应的Sponsor的时间:SponsorshipTimeout)——通过Configuration通过设置LifetimeServices的静态属性(LeaseTime,RenewOnCallTime,LeaseManagerPollTime,SponsorshipTimeout)同过Override MarshalByRefObj的InitializeLifetimeService。当Lease对象创建之后,Lease Manager会为Lease设置通过上面方式设定的属性。随后Lease Manager会每隔一定的时间(由LeaseManagerPollTime设定)轮询每个Lease,查看Lease是否过期;随着时间的推移,Lease的租期(InitialLeaseTime - Expired time)越来越少。在这期间,Clien端和Server端获得该Lease,调用Renew方法来延长Lease的租期;此外,来自Client端的远程调用也会把Lease的生命周期延长至一个设定的时间(由RenewOnCallTime设定)。

注:只有在Lease的生命周期小于由RenewOnCallTime设定的时间的条件下,远程调用才会对Lease的租期起作用,或者这样说:current lease time = MAX(lease time - expired time,renew on call time)

那么当Lease Manager在获知某个Lease的已经过期?他会做什么样的操作呢?它会马上结束该Lease吗?就像我在上一章所举的租房的例子一样,房东在房租到期之后,出于人性化的考虑,他会首先通知承租人是否有续租的意愿。如果有,可以续约。在Remoting中也存在这样一种状况,Client可以在Lease尚未到期的时候,为他注册一个或多个Sponsor,Lease Manger会首先联系注册到该Lease的Sponsor,如果获得这样的Sponsor,则调用Sponsor的Renewal,从而实现续约的目的。由于Sponsor处于Client端所在的Context,Lease Manager调用Sponsor实际上是一种远程调用,由于远程调用的不确定性,必须设定Lease Manager联系Sponsor的时间范围(由SponsorshipTimeout属性设定),如果超出这样的范围,则认为Sponsor不可得。

在Remoting中,一个Sponsor是一个是实现了System.Runtime.Remoting.Lifetime. ISponsor Interface的类对象。该接口只有一个成员方法:Renewal。还有一点需要特别说明的是,Spnosor是被设计来被处于Server端的Lease Manager调用的。由于这是一个跨AppDomain的调用,我们知道由于AppDomain的隔离性,在一个AppDomain创建的对象不能在另一个Appdomain中直接调用,需要经过一个Marshal的过程——Marshal By Refence 或者 Marshal By Value。我们一般采用Marshal By Refence的方式,我们经常使用的System.Runtime.Remoting.Lifetime.ClientSponsor就是直接继承自System. MarshalByRefObject。

 

namespace System.Runtime.Remoting.Lifetime
{
    
public interface ISponsor
    
{        
        TimeSpan Renewal(ILease lease);
    }

}

一旦Lease过期,在既定的时间内,不能获得对应的Sponsor(或者是Sponsor 的Renewal方法返回TimeSpan.Zero),那么Lease Manager就把该Lease标记为过期。如果这时有一个远程调用,Remoting Framework会通过Lease Manager得知Lease已经过期,如果Client Activation模式直接会抛出异常;如果是Singleton 模式的Server Activation,则会创建一个新的对象,原来对象的状态将不复存在。这里有一点需要特别注意的是,Lease Manager就把该Lease标记为过期,并不等于该Lease马上会被垃圾回收掉,同理,这时候虽然远程对象可能还是存在的,由于这时候我们不能保证调用的安全性——不能确定该对象什么时候被垃圾回收,对于远程调用来说,它已经没有任何意义。

从上面整个流程来看,为了保持远程对象,我们有Lease对象;为了保持Lease对象,我们有Sponsor对象,那么什么对象来保持Sponsor对象呢?那就要依赖于我们的Client代码了。如果注册的Sponsor对象如果一直不被回收的话,远程对象将永远存在。所以我们应该根据实际的需要,取消Sponsor对Lease的注册。

下面我们照例来展示一个Sample,在这个Sample中,我们设计一个计数器,获得某个对象某个方法的调用次数。

Step 1 :整个Solution的构架(Artech.LifetimeManagement.RemoteService被Artech.LifetimeManagement.Client和Artech.LifetimeManagement.Hosting引用)
 

 


Step 2:远程对象Artech.LifetimeManagement.RemoteService/CounterService.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting.Lifetime;

namespace Artech.LifetimeManagement.RemoteService
{
    
public class CounterService:MarshalByRefObject
    
{
        
private int _count;

        
public int GetCount()
        
{
            
this._count++;
            
return this._count;
        }

        
public CounterService()
        
{
            Console.WriteLine(
"Counter Object has been activated!");
        }

        
~CounterService()
        
{
            Console.WriteLine(
"Counter Object has been destroied!");
        }

        
public override object InitializeLifetimeService()
        
{
            ILease lease 
= (ILease)base.InitializeLifetimeService();
            
if (lease.CurrentState == LeaseState.Initial)
            
{
                lease.InitialLeaseTime 
= TimeSpan.FromSeconds(1);
                lease.RenewOnCallTime 
= TimeSpan.FromSeconds(1);
                lease.SponsorshipTimeout 
= TimeSpan.FromSeconds(1);
            }

            
return lease;
        }

    }

}

为了确定对象的创建和回收,我们定义了Constructor和重写了 Finalize方法。GetCouter是 远程调用的方法,每次调用,技术器一次递增并返回该计数。为了更容易地演示对象的生命周期,我们重写了InitializeLifetimeService,设置了一些列短时间的Lease属性(都为1s)。

Step 3 Host : Artech.LifetimeManagement.Hosting

App.config

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<system.runtime.remoting>
    
<application name="Artech.MyRemoting">
      
<service>
        
<wellknown type="Artech.LifetimeManagement.RemoteService.CounterService,Artech.
LifetimeManagement.RemoteService"

                  mode
="Singleton" objectUri="Counter.rem"></wellknown>
        
<activated type="Artech.LifetimeManagement.RemoteService.CounterService,Artech.
LifetimeManagement.RemoteService"
></activated>
      
</service>
      
<channels>
        
<channel type="System.Runtime.Remoting.Channels.Http.HttpChannel,System.
Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

                 port 
="1234">
          
<serverProviders>
            
<provider ref="wsdl" />
            
<formatter ref="binary" typeFilterLevel="Full" />
          
</serverProviders>
          
<clientProviders>
            
<formatter ref="binary" />
          
</clientProviders>
        
</channel>
      
</channels>
    
</application>
  
</system.runtime.remoting>
</configuration>

我定义了两个Service,一个WellKnown Service,一个CAO Service。因为我们将同时比较两种不同激活方式的生命周期的管理。在前面我们不止一次地说,调用Sponsor是一种远程调用,说得更确切地,只一种远程回调(Remote Callback),所以我们要把Type Filter Level设为Full,其原因可以参考我们文章([原创].NET Remoting: 如何通过Remoting实现双向通信(Bidirectional Communication)),在这里就不再说明。

Program.cs

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Runtime.Remoting;

namespace Artech.LifetimeManagement.Hosting
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            RemotingConfiguration.Configure(
"Artech.LifetimeManagement.Hosting.exe.config"false);
            Console.WriteLine(
"Calculator has begun to listen  ");
            GarbageCollect();
            Console.Read();
        }


        
static void GarbageCollect()
        
{
            
while (true)
            
{
                Thread.Sleep(
10000);                
                GC.Collect();
            }

        }

    }

}

通过上面的Code,我们先注册App.config的配置,为了更加清楚地看清对象的回收时间,我们每隔10s作一次垃圾回收。

Step 4 Client:Artech.LifetimeManagement.Client

App.config

 

<configuration>
  
<system.runtime.remoting>
    
<application>
      
<channels>
        
<channel ref="http" port="0">
          
<clientProviders>
            
<formatter ref="binary" />
          
</clientProviders>
          
<serverProviders>
            
<formatter ref="binary" typeFilterLevel="Full" />
          
</serverProviders>
        
</channel>
      
</channels>
    
</application>
  
</system.runtime.remoting>
</configuration>

由于处于Server端的Lease Manager要CallbackClient端的Sponsor,Client端必须注册一个Channel用于回调。同样把Type Filter Level设为Full。

Program.cs

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using Artech.LifetimeManagement.RemoteService;
using System.Threading;
using System.Runtime.Remoting.Lifetime;

namespace Artech.LifetimeManagement.Client
{
    
class Program
    
{
        
const int _invocationFrequency = 4;

        
static void Main(string[] args)
        
{
           RemotingConfiguration.Configure(
"Artech.LifetimeManagement.Client.exe.config"false);
            RemotingConfiguration.RegisterActivatedClientType(
typeof(CounterService), "http://localhost:1234/Artech.MyRemoting");

            CounterService singletonCounter 
= Activator.GetObject(typeof(CounterService),"http://localhost:1234/Artech.MyRemoting/Counter.rem")
                 
as CounterService;
            CounterService caoCounter 
= new CounterService();

            Thread singletonThread 
= new Thread(new ParameterizedThreadStart(InvocateCounterService));
            Thread caoThread 
= new Thread(new ParameterizedThreadStart(InvocateCounterService));

            singletonThread.Name 
= "Singleton";
            caoThread.Name 
= "CAO";
            
            singletonThread.Start(singletonCounter);
            caoThread.Start(caoCounter);
           
        }


        
static void InvocateCounterService(object counter)
        
{
            CounterService counterService 
= counter as CounterService;
            
while (true)
            
{
                
try
                
{
                    Console.WriteLine(
"{1}: The count is {0}", counterService.GetCount(), Thread.CurrentThread.Name.PadRight(10));
                    Thread.Sleep(_invocationFrequency 
* 1000);
                }

                
catch (Exception ex)
                
{
                    
if (Thread.CurrentThread.Name == "Singleton")
                    
{
                        Console.WriteLine(
"Fail to invocate Singleton counter because \"{0}\"", ex.Message);
                        
break;
                    }


                    
if (Thread.CurrentThread.Name == "CAO")
                    
{
                        Console.WriteLine(
"Fail to invocate CAO counter because \"{0}\"", ex.Message);
                        
break;
                    }


                }
                         
            }

        }
        
    }

}

通过上上面的Code,我首先创建了连个Proxy,一个Singleton和一个CAO。然后创建连个线程,并在这两个线程中以固定的时间间隔(4s:_ invocationFrequency = 4)通过这两个Proxy进行远程调用。

现在我们来看看运行的结果:

启动Hosting:
 

 


启动Client,等待一段时间:
 

 



再来看Host现在的显示:

 


我们可以看到,对于Singleton Proxy,调用没有出现异常,但是调用的计数器不没有维持一个持续的增长——从1到3,然后又从1-3,这样周而复始,这就证明了,没3次调用的远程对象不是同一个,当Lease过期之后,一个新的远程对象被创建。从Host 的输出也验证了这点,远程对象在不断地被创建。还有一个有意思的是,调用了3次Constructor之后才开始调用Finalizer方法,这说明了什么呢?这说明了,Lease过期后的调用,会导致新的远程对象的创建,但实际上这是,该远程对象还没有被回收。它在连续创建了3个新的对象后,才真正被垃圾回收。

而对于CAO Proxy,则不同,在第4次调用时,出现Exception,这Lease过期,再调用某个远程方法,会直接抛出Exception。

1.通过远程调用来延长远程对象的生命周期

通过我们开始的分析,在Lease的租约小于renew on call time,远程调用会使租约延长。按照这样理论,如果我们提高远程调用的频率,我们可以延长远程对象的生命周期。基于这样的想法,我们把效用的时间间隔从原来的4缩短为1s(_ invocationFrequency = 1)。再次运行。

Client端:


Host:

 

正如我们想得一样,无论是对于Singleton Proxy还是CAO Proxy,计数器一直维持一个持续增加的趋势,并且没有Exception抛出。从Client端就可以看出,Singleton Proxy和CAO Proxy调用的始终是一个远程对象,而Host的输出更是确凿地证明了,从始到终,只有连个对象被创建,一个对于Singleton,另一个对于CAO。

2.通过Lease来延长生命周期

上面我们通过远程调用来延长远程对象的生命周期,现在我们采用另一种方法,直接利用Lease对象来延长远程对象的生命周期。我们改动Client的代码:

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using Artech.LifetimeManagement.RemoteService;
using System.Threading;
using System.Runtime.Remoting.Lifetime;

namespace Artech.LifetimeManagement.Client
{
    
class Program
    
{
        
const int _invocationFrequency = 4;
        
const int _leaseRenewalFrequency = 1;

        
static void Main(string[] args)
        
{
           RemotingConfiguration.Configure(
"Artech.LifetimeManagement.Client.exe.config"false);
            RemotingConfiguration.RegisterActivatedClientType(
typeof(CounterService), "http://localhost:1234/Artech.MyRemoting");

            CounterService singletonCounter 
= Activator.GetObject(typeof(CounterService),"http://localhost:1234/Artech.MyRemoting/Counter.rem")
                 
as CounterService;
            CounterService caoCounter 
= new CounterService();

            Thread singletonThread 
= new Thread(new ParameterizedThreadStart(InvocateCounterService));
            Thread caoThread 
= new Thread(new ParameterizedThreadStart(InvocateCounterService));

Thread singletonLeaseRennewal 
= new Thread(new ParameterizedThreadStart(ExtendLifetimeViaLease));
            Thread caoLeaseRenewal 
= new Thread(new ParameterizedThreadStart(ExtendLifetimeViaLease));

            singletonThread.Name 
= "Singleton";
            caoThread.Name 
= "CAO";
            
            singletonThread.Start(singletonCounter);
            caoThread.Start(caoCounter);

  singletonLeaseRennewal.Start(singletonCounter);
            caoLeaseRenewal.Start(caoCounter);
           
        }


static void ExtendLifetimeViaLease(object counter)
        

            CounterService counterService 
= counter as CounterService;
            ILease lease 
= (ILease)RemotingServices.GetLifetimeService(counterService);
            
while (true)
            
{
                
if (lease == null)
                
{
                    Console.WriteLine(
"Can not retrieve the lease!");
                    
break;
                }


                lease.Renew(TimeSpan.FromSeconds(_leaseRenewalFrequency));
                Thread.Sleep(_leaseRenewalFrequency);
            }

        }


        
static void InvocateCounterService(object counter)
        
{
            CounterService counterService 
= counter as CounterService;
            
while (true)
            
{
                
try
                
{
                    Console.WriteLine(
"{1}: The count is {0}", counterService.GetCount(), Thread.CurrentThread.Name.PadRight(10));
                    Thread.Sleep(_invocationFrequency 
* 1000);
                }

                
catch (Exception ex)
                
{
                    
if (Thread.CurrentThread.Name == "Singleton")
                    
{
                        Console.WriteLine(
"Fail to invocate Singleton counter because \"{0}\"", ex.Message);
                        
break;
                    }


                    
if (Thread.CurrentThread.Name == "CAO")
                    
{
                        Console.WriteLine(
"Fail to invocate CAO counter because \"{0}\"", ex.Message);
                        
break;
                    }


                }
                         
            }

        }
        
    }

}

在上面的代码中,我通过ExtendLifetimeViaLease方法每个一定的时间(1s:_leaseRenewalFrequency = 1)对获得的Lease Renew 一次。  

static void ExtendLifetimeViaLease(object counter)
        

            CounterService counterService 
= counter as CounterService;
            ILease lease 
= (ILease)RemotingServices.GetLifetimeService(counterService);
            
while (true)
            
{
                
if (lease == null)
                
{
                    Console.WriteLine(
"Can not retrieve the lease!");
                    
break;
                }


                lease.Renew(TimeSpan.FromSeconds(_leaseRenewalFrequency));
                Thread.Sleep(_leaseRenewalFrequency);
            }

        }

象原来的例子一样,分别在连个线程中以一定时间间隔(4s)调用远程对象,不过这次我们创建两个新的线程不同对Lease进行Renew,这样确保Lease用不过期。实验证明,输出结果和上面完全一样。

3.通过Sponsor来延长生命周期 

不说废话直接来看代码:

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using Artech.LifetimeManagement.RemoteService;
using System.Threading;
using System.Runtime.Remoting.Lifetime;

namespace Artech.LifetimeManagement.Client
{
    
class Program
    
{
        
const int _invocationFrequency = 4
        
static ISponsor _singletonSponsor;
        
static ISponsor _caoSponsor;

        
static void Main(string[] args)
        
{
            RemotingConfiguration.Configure(
"Artech.LifetimeManagement.Client.exe.config"false);
            RemotingConfiguration.RegisterActivatedClientType(
typeof(CounterService), "http://localhost:1234/Artech.MyRemoting");
            CounterService singletonCounter 
= Activator.GetObject(typeof(CounterService),"http://localhost:1234/Artech.MyRemoting/Counter.rem")
                 
as CounterService;
            CounterService caoCounter 
= new CounterService();

            Thread singletonThread 
= new Thread(new ParameterizedThreadStart(InvocateCounterService));
            Thread caoThread 
= new Thread(new ParameterizedThreadStart(InvocateCounterService));            

            singletonThread.Name 
= "Singleton";
            caoThread.Name 
= "CAO";
            
            singletonThread.Start(singletonCounter);
            caoThread.Start(caoCounter);

            _singletonSponsor 
= ExtendLifetimeViaSponsor(singletonCounter);
            _caoSponsor 
= ExtendLifetimeViaSponsor(caoCounter);
           
        }


        
static void InvocateCounterService(object counter)
        
{
            CounterService counterService 
= counter as CounterService;
            
while (true)
            
{
                
try
                
{
                    Console.WriteLine(
"{1}: The count is {0}", counterService.GetCount(), Thread.CurrentThread.Name.PadRight(10));
                    Thread.Sleep(_invocationFrequency 
* 1000);
                }

                
catch (Exception ex)
                
{
                    
if (Thread.CurrentThread.Name == "Singleton")
                    
{
                        Console.WriteLine(
"Fail to invocate Singleton counter because \"{0}\"", ex.Message);
                        
break;
                    }


                    
if (Thread.CurrentThread.Name == "CAO")
                    
{
                        Console.WriteLine(
"Fail to invocate CAO counter because \"{0}\"", ex.Message);
                        
break;
                    }


                }
                         
            }

        }



        
static ISponsor ExtendLifetimeViaSponsor(CounterService counter)
        
{
            CounterService counterService 
= counter as CounterService;
            ILease lease 
= (ILease)RemotingServices.GetLifetimeService(counterService);
            ClientSponsor sponsor 
= new ClientSponsor(TimeSpan.FromSeconds(4));
            sponsor.Register(counterService);
            
return sponsor;
        }

    }

}

上面的代码中,我通过ExtendLifetimeViaSponsor方法为Lease注册一个Sposnor,并把Renew时间设为4s,最后把该Sposnor负值给一个静态变量,这样他不会被垃圾回收。那么每次Lease Manager获得该Sponsor时候,会自动把Lease 的租期变为4s。这样远程对象将会永久存活。可以想象,输出结果将会和上面一样。 

 

我所理解的Remoting(3):CAO Service Factory使接口和实现相分离

 我们知道对于Remoting,有两种不同的Activation模式:Server Activation和Client Activation。他我在前面的系列文章中分析、比较了这两种不同激活方式的区别:Marshaling方式,远程对象创建的时机,状态的保持,生命周期的管理。 在编程模式方面Server Activation和Client Activation也具有一定的差异:为一个SAO(server activated object)和一个CAO(client activated object)注册一个远程对象类型的方式是不同的(Wellknown Service Type Re V.S. Activated Type Registration);为为一个SAO(server activated object)和一个CAO(client activated object)创建Proxy的方式也不一样:对于SAO,一般通过Activator的静态方法GetObject(传入一个远程对象的地址);而我们一般通过new 关键字或者Activator的静态方法CreateInstance。

 

String remoteAddress = "http://localhost/Artech.CAOFactory/CounterFactory.rem";        
ICounter counter 
= ICounterFactory counterFactory = (ICounterFactory)Activator.GetObject(typeof(ICounterFactory), remoteAddress);

对于Client Activation,由于我们在创建Proxy对象的时候,必须利用远程对象对应的原数据,所以在Client端,需要引用远程的对象所对应的dll。比如我们现在做一个简单的计数器的例子(Client远程调用获得计数器当前的计数)我们把业务逻辑封装在Counter Service的实体中。下图反映了这样一种架构的依赖关系。 
 


经验丰富的开发人员很快会意识到这是一种很不好的分布式构架。从SOA的角度来讲也是不值得推荐的构架方式。SOA崇尚的是Contract层面的共享,而拒绝Type层面的共享。Common Type增加了交互双方的依赖性,造成的紧耦合。所以我们一般从Service中把相对静态的Contract(可以简单地把 Contract看成是Service提供的所有操作的列表和调用的接口)提取出来,作为双方交互的契约:Client只要满足这个Contract,它就能够调用相应的Service,而Service 真正实现的改变对Client没有任何的影响,实际上Service的实现对于Client来说是完全透明的。我们可以说基于Contract的共享成就了SOA的松耦合。通过提取Contract之后,各个实体成为了下面一种依赖关系。
 

 



但是对于Client Activation,要直接实现这样的构架是不可能的。我们已经说过,Client创建一个CAO Proxy,需要和Host端注册的远程类型对应的原数据,换句话说,如果远程类型实现在CounterService的dll中,Host和Client双方都需要引用这个dll——虽然实现部分的代码对Client毫无意义。但是现在我们的目的的吧这个dll仅仅驻留在Host中,Client只需引用存储Contract的dll。

在一个分布式环境中,一个Application要跨AppDomain调用一个驻留在另一个AppDomain的的方法,他不需要获得这个真正的远程对象(而实事上它也不可能获得在另一个AppDomain中创建的对象),它只需要获得该对象的一个引用(说得具体点,它只需要获得该对象的ObjRef),并根据这个引用创建相应的Proxy来进行远程调用。或者说,我们只要通过某种方法把Server端创建的对象通过Marshaling传递到Client端,Client就可以进行远程调用了。

那么如何为一个远程调用从另一个AppDomain中获取一个远程对象的引用并创建Proxy呢?而这个获取的方式本身也是一个远程调用。我们的做法是:通过一个基于SAO的远程调用获取一个远程对象的引用并同时创建Proxy。而这个Proxy对应的远程对象就像当于一个CAO.

下面是我们的解决方案简要的类图。我们整个基于计数器的Service封装在CounterService中,它实现了ICounter接口,CounterFactoryService用于创建一个CounterService对象,它实现的接口是ICounterFactory。
 

 



现在我们就来实现它:

Step 1: 建立这个Solution的整体结构

整个Solution包含3个Project:Artech.CAOFactory.Contract;Artech.CAOFactory.Service;Artech.CAOFactory.Client。Artech.CAOFactory.Contract被Artech.CAOFactory.Service和Artech.CAOFactory.Client引用。我们使用IIS的Host方式,从而省略了Host Application。
 

 



Step 2 创建Contract

ICounter

 

using System;
using System.Collections.Generic;
using System.Text;

namespace Artech.CAOFactory.Contract
{
  
public   interface ICounter
    
{
      
int GetCount();
    }

}

ICounterFactory

 

using System;
using System.Collections.Generic;
using System.Text;

namespace Artech.CAOFactory.Contract
{
    
public interface ICounterFactory
    
{
        ICounter CreateService();
    }

}

Step 3 实现Contract:Artech.CAOFactory.Service

 

using System;
using System.Collections.Generic;
using System.Text;
using Artech.CAOFactory.Contract;

namespace Artech.CAOFactory.Service
{
    
public class CounterService : MarshalByRefObject,ICounter
    
{
        
private int _count;

        
ICounter Members
    }

}

CounterFactoryService

using System;
using System.Collections.Generic;
using System.Text;
using Artech.CAOFactory.Contract;

namespace Artech.CAOFactory.Service
{
    
public class CounterFactoryService :MarshalByRefObject, ICounterFactory
    
{
        
        
ICounterFactory Members
    }

}

Step 3 通过IIS Host CounterFactoryService

修改编译配置把Dll生成在Project根目录的bin目录下,基于这个根目录创建虚拟目录(假设Alias就是Artech.CAOFactory),并添加Web.config。

<?xml version="1.0"?>
<configuration>
    
<configSections>
        
<section name="WorkflowRuntime" type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    
</configSections>
    
<WorkflowRuntime Name="WorkflowServiceContainer">
        
<Services>
            
<add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
            
<add type="System.Workflow.Runtime.Hosting.DefaultWorkflowCommitWorkBatchService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        
</Services>
    
</WorkflowRuntime>
    
<appSettings/>
    
<connectionStrings/>
    
<system.web>        
        
<compilation debug="false"/>        
        
<authentication mode="Windows"/>        
    
</system.web>
    
<system.runtime.remoting>
        
<application>
            
<service>
                
<wellknown type="Artech.CAOFactory.Service.CounterFactoryService, Artech.CAOFactory.Service"
                           mode 
="SingleCall" objectUri="CounterFactory.rem"></wellknown>
            
</service>
        
</application>
    
</system.runtime.remoting>
</configuration>

Step 4 创建客户端Artech.CAOFactory.Client

Program

 

using System;
using System.Collections.Generic;
using System.Text;
using Artech.CAOFactory.Contract;
using System.Threading;
using System.Runtime.Remoting;

namespace Artech.CAOFactory.Client
{
    
class Program
    
{
        
const string REMOTE_ADDRESS = "http://localhost/Artech.CAOFactory/CounterFactory.rem";
        
static void Main(string[] args)
        
{
            ICounterFactory counterFactory 
= (ICounterFactory)Activator.GetObject(typeof(ICounterFactory), REMOTE_ADDRESS);
            ICounter counter 
= counterFactory.CreateService();

            
while (true)
            
{
                Console.WriteLine(
"The current value of the counter is {0}", counter.GetCount());
                Thread.Sleep(
5000);
            }

        }

    }

}

从上面的代码我们可以看到,我们希望使用的远程对象的Proxy(counter),是通过另一个SongleCall Proxy(counterFactory)获得的。

下面来运行,从输出结果来看,和我们平常使用的SAO方式的结果没有什么两样——同一个Proxy之间的调用状态被保留。

原文地址:https://www.cnblogs.com/pengyou8696/p/1898993.html