Remoting基本原理及其扩展机制(ZZ)

 

.NET Remoting是.NET平台上允许存在于不同应用程序域中的对象相互知晓对方并进行通讯的基础设施。调用对象被称为客户端,而被调用对象则被称为服务器或者服务器对象。简而言之,它就是.NET平台上实现分布式对象系统的框架。

传统的方法调用是通过栈实现,调用方法前将this指针以及方法参数压入线程栈中,线程执行方法时将栈中的参数取出作为本地变量,经过一番计算后,将方法的返回结果压入栈中。这样我们就完成了一次方法调用。如下图所示:



基于栈的方法调用在同一个应用程序域中很容易实现,但是如果要调用的方法所属的对象位于另一个应用程序域或另一个进程甚至是另一个机器,又当如何?应用程序域之间是无法共享同一个线程栈的,此时我们将转而使用另一种方法调用机制——基于消息的方法调用机制。在客户端通过代理对象将原先基于栈的方法调用信息(定位远程对象的信息、方法名、方法参数等)封装到一个消息对象中,再根据需要将这些消息对象转化成某个格式的数据流发送到远程对象所在的的应用程序域中。当经过格式化的消息到达服务器后,首先从中还原出消息对象,之后在远程对象所在的的应用程序域中构建出相应方法调用栈,此时就可以按照传统的基于栈的方法调用机制完成方法的调用,而方法返回结果的过程则按照之前的方法反向重复一遍。如下图所示:

在基于消息的远程方法调用中主要有以下几个重要角色:

Client Proxy: 负责在客户端处理基于栈的参数传递模式和基于消息的参数传递模式之间的转换。

Invoker:与Client Proxy的功能相反。

Requestor: 负责将消息对象转换成可在网络上传输的数据流,并将其发送到服务器。

Marshaller: 负责消息对象的序列化与反序列化。

Client Request Handle:负责以数据流的格式发送客户端的请求消息。

Server Request Handel:负责接收来自客户端的请求消息。

那么在.NET Remoting框架下,这些重要角色又各自对应了哪些对象呢?下图是一个Remoting框架的示意图:


图1

从中我们可以看到客户端的Transparent Proxy与服务器端的StackBuilderSink分别扮演了Client Proxy与Invoker的角色。Remoting依靠这两个对象实现了基于栈的方法调用与基于消息的方法调用的转换,并且这一过程对于开发者是完全隐藏的。

Marshaller的角色由Formmatter Sink完成,在Remoting中默认提供了两种Fommatter:一个实现了消息对象与二进制流的相互转换,另一个实现了消息对象与Soap数据包的相互转换,而支持Soap格式则说明Remoting具有实现Web Service技术的可能。

Client Request Handle与Server Request Handel都是.NET中实现网络底层通讯的对象,如HttpWebRequest、HttpServerSocketHandler等。在Remoting中我们并不直接接触这些对象,而是通过Channel对它们进行管理。在框架中默认提供了三种Channel:HttpChannel、TcpChannel与IpcChannel。但是在上面这副图中,我们并没有看到Channel对象,那么Channel又是如何影响网络底层的通讯协议的呢?

其实在上面这幅图中,真正能对通讯时所采用的网络协议产生影响的元素是Transport Sink,对应不同的协议Remoting框架中提供了三种共计六个Transport Sink:HttpClientTransportSink、HttpServerTransportSink与TcpClientTransportSink、TcpServerTransportSink以及IpcClientTransportSink、IpcServerTransportSink,它们分别放置在客户端与服务器端。既然Transport Sink才是通讯协议的决定元素,那么Channel肯定与它有着某种联系,让我们先暂时搁置此话题,留待后面进一步介绍。

观察上面这副图,我们可以发现其中包含了一系列的Sink,而所谓的Sink就是一个信息接收器,它接受一系列的输入信息,为了达到某种目的对这些信息做一些处理,然后将处理后信息再次输出到另一个Sink中,这样一个个的Sink串联起来就构成了一个Pipeline(管道)。Pipeline模式在分布式框架中经常可以看到,应用该模式可以使框架具有良好的灵活性。当我们需要构建一个系统用于处理并转换一串输入数据时,如果通过一个大的组件按部就班的来实现此功能,那么一旦需求发生变化,比如其中的两个处理步骤需要调换次序,或者需要加入或减去某些处理,系统将很难适应,甚至需要重写。而Pipeline模式则将一个个的处理模块相互分离,各自独立,然后按照需要将它们串联起来即可,此时前者的输出就会作为后者的输入。此时,每个处理模块都可以获得最大限度的复用。当需求发生变化时,我们只需重新组织各个处理模块的链接顺序,或者删除或加入新的处理模块即可。在这些处理模块(Sink)中最重要的两类是Formatter Sink与Transport Sink,也就是图1中的红色部分,当我们需要通过网络访问远程对象时,首先要将消息转化为可在网络上传播的数据流,然后需要通过特定的网络协议完成数据流的发送与接收,这正是这两类Sink所负责的功能。虽然它们本身也可以被自定义的Sink所替换,不过Remoting中提供的现有实现已经可以满足绝大多数的应用。

那么.NET Remoting中又是如何创建这个Pipeline的呢?以HttpChannel为例,在创建客户端的Pipeline的时候,会调用它的CreateMessageSink方法,此时又会进一步调用HttpClientChannel的构造函数,在构造函数中调用了一个名为SetupChannel()的私有方法,看一下它的实现: 

         private   void  SetupChannel()
        
{
            
if(this._sinkProvider != null)
            
{
                CoreChannel.AppendProviderToClientProviderChain(
this._sinkProvider,
                    
new HttpClientTransportSinkProvider(this._timeout));
            }

            
else
            
{
                
this._sinkProvider = this.CreateDefaultClientProviderChain();   
            }

        }

再看看在没有提供自定义SinkProvider的默认情况下CreateDefaultClientProviderChain()会创建出哪些Sink

         private  IClientChannelSinkProvider CreateDefaultClientProviderChain()
        
{
            IClientChannelSinkProvider provider 
= new SoapClientFormatterSinkProvider();
            provider.Next 
= new HttpClientTransportSinkProvider(this._timeout);
            
return provider;
        }

其中包含了SoapClientFormatterSinkProvider与HttpClientTransportSinkProvider,自然地,到了创建Pipeline的时候,它们会分别创建出SoapClientFormatterSink与HttpClientTransportSink,前者用于实现消息对象的Soap格式化,而后者则表示将用Http协议来实现消息的通讯。这样我们就明白了为什么使用HttpChannel就会采用Http协议作为网络通讯协议,并且消息将以SOAP格式传递。但是仔细观察SetupChannel方法中的下段代码,我们可以发现通过提供自定义的SinkProvider,我们可以改变消息的编码格式,因为此时只创建了一个HttpClientTransportSinkProvider,并没有定义FormatterSinkProvider,而FormatterSinkProvider完全可以在自定义的SinkProvider中自由设定。

在上文中我们已经介绍到通过在配置文件中指定自定义的ChannelSinkProvider,我们可以在Pipeline中加入自己的ChannelSink,此时我们就可以加入自己的信息处理模块,但是这里我们所能操作的对象是已经经过格式化的消息(即数据流),我们看不到原始的消息对象,这也势必影响了我们所能实现的扩展功能。而在上文的图1中,我们看到除了ChannelSink可以扩展之外,我们还可以加入自定义的MessageSink,而它是位于格式器之前的,也就是说在MessageSink中我们可以直接操作尚未格式化的消息对象。此时,我们就获得一个功能更强大的扩展点。直接操作消息对象,这意味着什么呢?简单来说,我们可以在这里实现方法拦截,我们可以修改方法的参数、返回值,在调用方法前后加入自己的处理逻辑。是不是觉得听上去很耳熟?没错,这就也正是AOP所要实现的一个目标。下面,在了解了整个Remoting的大背景以及ChannelSink的扩展机制后,我们将对MessageSink的扩展机制做进一步介绍。

在介绍前,我先提醒各位读者注意以下几点:

1.  确定你确实想深入了解Remoting的内部机制;

2.  确定你能很好的理解上一篇文章;

3.  如果说上一篇文章总结归纳的内容较多的话,在本文中出现的内容大多是笔者个人的探索,我想其他资料(包括英文资料)中都不曾介绍过这些内容,所以我不保证所有观点的正确性,如果你觉得哪里有误,也欢迎你在评论中提出你的意见。

下面就让我们开始品尝大餐吧。 :)

利用ChannelSinkProvider扩展MessageSink

MessageSink的扩展有两种实现方法,让我先从简单的开始。在上一篇文章我们已经介绍到通过在配置文件中指定自定义的ChannelSinkProvider,我们可以在Pipeline中加入自己的ChannelSink。那么有没有一个类似于IClientChannelSinkProvider的IMessageSinkProvider呢?可惜答案是否定的。那么我们能否通过IClientChannelSinkProvider插入一个MessageSink呢?插入之后它又能否发挥其功效呢?

首先我们先实现一个自定义的MessageSink。此时只需新建一个类,并实现IMessageSink接口中的SyncProcessMessage方法(为简单起见我们只考虑同步调用模式),在方法中我们可以直接操作Message对象,比如我们可以向Message中加入额外的属性,如下所示:

         public   class  CustomMessageSink : IMessageSink
        
{
            
public IMessage SyncProcessMessage(IMessage msg)
            
{
                
// Add some custom data into msg.
                ((IMethodMessage)msg).LogicalCallContext.SetData("MyName""idior");
                
return m_NextSink.SyncProcessMessage( msg );
            }

        }

上文的图2中我们可以看到IClientChannelSinkProvider是通过下面这个方法创建ChannelSink。

             public  IClientChannelSink CreateSink(IChannelSender channel,  string  url,  object  remoteChannelData)
            
{
                
// ...
            }

注意它的返回值是IClientChannelSink,而不是IMessageSink,这样我们就无法将仅实现了IMessageSink接口的CustomMessageSink插入。为此,我们让CustomMessageSink也实现IClientChannelSink接口,只不过在实现IClientChannelSink接口中的方法时,我们全部抛出异常,以表示这些方法不应该被调用到。这样我们就可以瞒天过海般地利用ChannelSinkProvider创建出一个MessageSink。现在问题来了,这个MessageSink虽然创建出来了,但是它被插入Pipeline了吗?其实,我们在上一篇文章中就漏过了一个问题——那些利用ChannelSinkProvider创建出来的ChannelSink是如何被插入到Pipeline中的,明白了它的原理,就自然解决了上面的问题。

Pipeline

Pipeline是何物?我们并没有解释清楚这个概念,是否存在一个对象它就叫Pipeline或者类似的名字?遗憾地告诉你,没有!可以说这里的Pipeline是一个抽象的概念,它表示了当我们调用一个远程对象时从RealProxy到StackBuildSink之间所经过的一系列Sink的集合,但是并不存在一个单独的链表把这些Sink全部链接起来。也就是说并不存在一个大的Sink链表,当你触发远程方法后,我们就依次从这个链表中取出一个个的Sink,大家挨个处理一下消息。不过在远程对象的代理中倒是维护了一个由ChannelSink组成的链表。不过需要注意它并不代表整个Pipeline,而只能算是其中一部分,在后面我们会看到Pipeline中还包括了很多其他类型的Sink。这个链表保存在RealProxy的_identity对象中,链表是通过IClientChannelSink的Next属性链接起来的,在_identity对象中保存链表的第一个元素,其他元素可以通过Next属性获得,如下图所示:




下面我们来看看这个链表是如何得到的。每当我们通过TransparentProxy调用远程方法时,如下图所示,最终会调用到RemotingProxy中的InternalInvoke方法,它将负责把各个ChannelSink创建出来并链接在一起。



         internal   virtual  IMessage InternalInvoke(IMethodCallMessage reqMcmMsg,  bool  useDispatchMessage,  int  callType)
        

            
//...
            if(identity.ChannelSink == null)
            
{
                IMessageSink envoySink 
= null;
                IMessageSink channelSink 
= null;
                
if (!identity.ObjectRef.IsObjRefLite())
                
{
                    RemotingServices.CreateEnvoyAndChannelSinks(
null,identity.ObjectRef, out envoySink , out channelSink );
                }

                
else
                
{
                    RemotingServices.CreateEnvoyAndChannelSinks(identity.ObjURI, 
nullout envoySink , out channelSink );
                }

                RemotingServices.SetEnvoyAndChannelSinks(identity, envoySink, channelSink);
                
if (identity.ChannelSink == null)
                
{
                    
throw new RemotingException("...");
                }

            }

            
//...
        }

第一个判断语句( Line 5)说明创建并链接ChannelSink的工作只发生在第一次调用,以后的每次调用将重复使用第一次的结果。第二个判断语句( Line 9)暂且不管,我只需知道在下一步将创建出两个Sink链,一个是EnvoySinl链,而另一个是ChannelSink链,前者我们也先不去管它(将在下部中介绍)而后者将通过out关键字传给局部变量channelSink。其中CreateEnvoyAndChannelSinks方法最终会把ChannelSink链的创建任务交给Channel对象,至于Channel对象是如何配合ChannelSinkProvider工作的,我们在上一篇文章中已经介绍过了。

不知你有没有注意到局部变量channelSink(Line 8)此时的类型是IMessageSink 而不是IClientChannelSink。到关键地方了,大家提起精神啊!明明我们创建的是ChannelSink链却把头元素的类型设为IMessageSink 。这是为什么?大家知道在采用HttpChannel时,ChannelSink链的一个元素是什么吗?——SoapClientFormatterSink。你认为它应该是一个Message Sink还是Channel Sink?它是负责将消息对象格式为数据流的,操作对象是原始消息,自然应该是一个MessageSink。呵呵,原来搞了半天Remoting本身就有一个利用IClientChannelSinkProvider扩展MessageSink的例子(你可以在类库中找到SoapClientFormatterSinkProvider)。如之前所述,SoapClientFormatterSink虽然是一个MessageSink,但是为了利用IClientChannelSinkProvider将其插入到Pipeline中,它也不得不实现IClientChannelSink接口,而且你可以看到它在实现IClientChannelSink接口中的方法时,全部抛出异常。如下所示:

     public   class  SoapClientFormatterSink :IMessageSink, IClientChannelSink // ...
     {
        
//...

        
//Implement method in IMessageSink
        public IMessage SyncProcessMessage(IMessage msg)
        
{
            IMethodCallMessage message1 
= (IMethodCallMessage) msg;
            
try
            
{
                ITransportHeaders headers1;
                Stream stream1;
                Stream stream2;
                ITransportHeaders headers2;
                
this.SerializeMessage(message1, out headers1, out stream1);
                
this._nextSink.ProcessMessage(msg, headers1, stream1, 
                    
out headers2, out stream2);
                
if (headers2 == null)
                
{
                    
throw new ArgumentNullException("returnHeaders");
                }

                
return this.DeserializeMessage(message1, headers2, stream2);
            }

            
catch (Exception exception1)
            
{
                
return new ReturnMessage(exception1, message1);
            }

            
catch
            
{
                
return new ReturnMessage(new Exception("...")), message1);
            }

        }

 
        
//Implement method in IClientChannelSink
        public void ProcessMessage(...)
        
{
            
throw new NotSupportedException();
        }

        
//...
    }

然后在InternalInvoke方法中(代码3),我们又通过SetEnvoyAndChannelSinks方法(Line19)把之前赋值的局部变量channelSink赋给RemotingProxy对象中identity对象的_channelSink变量,这样一个个ChannelSink被链接在一起并能被代理对象所访问。

现在我们可以确定通过IClientChannelSinkProvider完全可以向Pipeline中插入新的MessageSink。由于SoapClientFormatterSink的存在,我们也完全可以相信这个被插入到ChannelSink链中的MessageSink能正常的工作(即执行IMessageSink中的方法,而不是IClientChannelSink中的方法),不过为了让大家更清楚Remoting的底层实现,我们还是想探究一下它是如何调用ChannelSink链中的一个个Sink来处理消息的。下图就是调用一次远程方法所产生的序列图:


查看原图

在上图中,我们可以看到在InternalInvoke方法中将调用CallProcessMessage方法,它会把消息对象交给ChannelSink链中的第一个Sink处理。如下所示:
RemotingProxy.CallProcessMessage(identity.ChannelSink,reqMsg,...);

而我们在上图中可以发现CallProcessMessage方法的第一个形参是IMessageSink类型的。也就是说通过IClientChannelSinkProvider方式插入到Pipeline中的第一个Sink,反倒是IMessageSink类型的,而不是IClientChannelSink。这也为插入到ChannelSink链中的MessageSink能正常工作扫清了障碍。正是因为这个原因SoapClientFormatterSink才能发挥其作用。

另外在利用IClientChannelSinkProvider插入MessageSink的时候,必须将它插入到FormatterSink的前面。因为只有在消息被Formmat之前,我们才能通过MessageSink对它进行处理,Format之后在Sink对消息的修改就无效了。这点在配置文件中体现为自定义的SinkProvider必须放在Formatter前面。不过这是针对客户端而言,服务器端则恰恰与此相反。

<channel ref="http">
   <clientProviders>
      <provider type="CustomSinks.CustomSinkProvider,CustomSinks" />
      <formatter ref="soap" />
   </clientProviders>
</channel>


而在客户端插入ChannelSink时,自定义的SinkProvider都是放在Formatter后面的。你可以在上文的图2中发现这点。

总结
在本节中主要介绍了如何利用IClientChannelSinkProvider向Pipeline中加入MessageSink,从而在远程方法调用中修改消息对象,实现功能更强大的扩展。并由此介绍了Remoting在实现此功能时,它的内部实现机制,有助于大家更深入地了解Remoting框架。

让我们在继续开始之前先了解以下几个基本概念。

应用程序域

应用程序域(通常简称为AppDomain)可以视为一种轻量级进程。一个Windows进程内可以包含多个AppDomain。AppDomain这个概念的提出是为了实现在一个物理服务器中承载多个应用程序,并且这些应用能够相互独立。ASP.NET中利用AppDomain在同一个进程内承载了多组Web应用程序就是一个例子。实际上微软曾进行过在单一进程内承载多达1000个简单Web应用程序的压力测试。

使用AppDomain所获得的性能优势主要体现在两方面:

  • 创建AppDomain所需要的系统资源比创建一个Windows进程更少。
  • 同一个Windows进程内所承载的AppDomain之间可以互相共享资源,如CLR、基本.NET类型、地址空间以及线程。

而各个AppDomain之间的独立性体现为以下这些特征:

  • 一个AppDomain可以独立于其他的AppDomain而被卸载。
  • 一个AppDomain无法访问其他AppDomain的程序集和对象。
  • 若没有发生跨边界的异常抛出,一个AppDomain拥有自己独立的异常管理策略。这意味着一个AppDomain内出现问题不会影响到同一个进程内中的其他AppDomain。
  • 每个AppDomain可以分别定义独自的程序集代码访问安全策略。
  • 每个AppDomain可以分别定义独自的规则以便CLR在加载前定位程序集所在位置。

可以看出应用程序域是进程中的一个子单元,不过在.NET中还存在一个比应用程序域还要细粒度的单元——.NET上下文(Context)。

.NET Context

一个.NET 应用程序域能够包含多个被称为.NET上下文的实体。所有.NET对象都存在于上下文中,每个应用程序域中至少存在一个上下文。这个上下文称为应用程序域的默认上下文,它在应用程序域创建的时候就创建了。下图总结了它们之间的关系:


那么MessageSink与上下文有什么关系呢? 我们知道在通常情况下,如果访问同一个AppDomain中对象的方法时,会采用基于栈的方式(详见本系列上部)。在这种情况下,我们是无法拦截其中的消息的,因为此时根本不存在消息对象。只有当我们通过Transparent Proxy访问另一个对象的方法时,才会采用基于消息的方式。而现在我们只知道当一个对象调用处在另一个AppDomain中的远程对象(该对象为MarshalByRefObject子类)时,Remoting才会为调用方创建那个远程对象的Transparent Proxy。在了解了.NET上下文的概念后,你会发现,即使处在同一个AppDomain中的两个对象,如果它们所处的上下文不同,在访问对方的方法时,也会借由Transparent Proxy实现,即采用基于消息的方法调用方式。此时,我们就可以在上下文中插入MessageSink了。那么在上下文中是否存在类似IClientChannelSinkProvider的接口呢?很幸运,在经过一番探索后,我们发现确实存在类似的接口,而且还不止一个:IContributeEnvoySink、IContributeServerContextSink、IContributeObjectSink、IContributeClientContextSink这四个接口中各自包含了一个GetXXXSink的方法,它们都会返回一个实现了IMessageSink接口的对象。我们知道IClientChannelSinkProvider接口是配合配置文件最终实现向Pipeline中加入ChannelSink的,而以上这四个接口并没有配合配置文件使用,不过与IClientChannelSinkProvider的使用方式倒也有异曲同工之效。读者可以将下面这幅图与本系列上部中的图2比较一番。


查看原图

在上图中又出现了很多新的概念,下面将对它们一一做出解释:

ContextBoundObject

上下文可以看作应用程序域中一个包含对象和消息接收器的区域。对上下文里的对象的调用会转换成可以被MessageSink(消息接收器)拦截和处理的消息。我们知道要把调用转换成消息,必须通过透明代理这个中介。而且,仅当对象是MarshalByRefObject的子类的实例并被其所在的应用程序域以外的实体调用时,CLR才会为它创建透明代理。这里,我们希望对所有调用使用消息接收器机制,即使那些调用是来自同一个应用程序域中的实体。这个时候我们就需要用到System.ContextBoundObject类了。继承自ContextBoundObject的类的实例同样仅能由透明代理访问。此时,即使在这个类的方法中使用的this引用也是透明代理而不是对这个对象的直接引用。我们会发现ContextBoundObject类继承自MarshalByRefObject,这非常合理,因为它很好地强调了该类的特性——它告诉CLR这个类将会通过透明代理使用。

ContextBoundObject的子类的实例被视为上下文绑定的(context-bound)。没有继承自ContextBoundObject的类的实例则被视为上下文灵活的(context-agile)。上下文绑定的对象永远在其上下文中执行。只要不是远程对象,上下文灵活的对象总是在执行这个调用的上下文中执行。如下图所示:


ContextAttribute

上下文attribute是应用在上下文绑定的类上的.NET attribute。上下文attribute类实现了System.Runtime.Remoting.Contexts.IContextAttribute接口。上下文绑定的类可以应用多个上下文attribute。在这个类的对象创建期间,这个类的每个上下文attribute判断这个对象的创建者所在的上下文是否适用。该操作通过以下方法完成:

public bool IContextAttribute.IsContextOK(Context clientCtx,
IConstructionCallMessage ctorMsg)

只要其中一个上下文attribute返回false,CLR就必须创建一个新的上下文来容纳这个新的对象。这样,每个上下文attribute可以在这个新的上下文中注入一个或多个上下文属性。这些注入通过以下方法完成:

public void IContextAttribute.GetPropertiesForNewContext(
IConstructionCallMessage ctorMsg)

IContextProperty

上下文属性是实现System.Runtime.Remoting.Contexts.IContextProperty接口的类的实例。每个上下文可以包含多个属性。上下文属性在上下文创建的时候通过上下文attribute注入。一旦每个上下文attribute注入了它的属性,就会为每个属性调用下面的方法。此后就无法在这个上下文中注入另外的属性了:

public void IContextProperty.Freeze( Context ctx )

然后,CLR通过调用下面的方法判断新的上下文能否满足每个属性:

public bool IContextProperty.IsNewContextOK( Context ctx )

每个上下文属性都有一个通过Name属性定义的名称:

public string IContextProperty.Name{ get }

上下文中承载的对象的方法可以通过调用下面的方法访问上下文属性:

IContextProperty Context.GetProperty( string sPropertyName )

这一点很有意思,上下文中的对象通过它们所在的上下文的属性可以共享信息并访问服务。不过,上下文属性的主要作用并不在于此。上下文属性的主要作用在于向相关上下文中的消息接收器区域注入消息接收器(MessageSink)。(消息接收器区域的概念将在后面介绍)

以上注入MessageSink的过程可以用下图概括:



MessageSink Region

不知你是否记得之前提到的四个接口:IContributeEnvoySink、IContributeServerContextSink、IContributeObjectSink、IContributeClientContextSink。其实它们分别代表了四个不同的消息接收器区域:服务器(server)区域、对象(object)区域、信使(envoy)区域和客户端(client)区域。要理解区域概念,你必须考虑上下文绑定的对象是否被位于另一个上下文的实体调用。这个实体可以是一个静态方法或者另一个对象。在我们关于区域的讨论中,我们把这个实体所在的上下文称为调用方上下文(calling context),而把被调用对象所在的上下文称为目标上下文(target context)。目标上下文中的每个属性都可以在这些区域中注入消息接收器。

  • 注入服务器区域的消息接收器拦截所有从另一个上下文发往目标上下文中所有对象的调用消息。于是,每个目标上下文有一个服务器区域。
  • 注入对象区域的消息接收器拦截所有从另一个上下文发往目标对象中特定对象的调用消息。于是,上下文中每个对象会有一个对象区域。
  • 注入信使区域的消息接收器拦截所有从另一个上下文发往目标对象中特定对象的调用消息。信使区域和对象区域的不同点是信使区域位于调用方上下文而不是包含对象的目标上下文。我们使用信使区域把调用方上下文的信息传递给目标上下文的消息接收器。
  • 注入客户端区域的消息接收器拦截所有从目标上下文发往位于其他上下文的对象的调用消息。于是,每个目标上下文有一个客户端区域。你可能会对这个区域所处的位置有点困惑,似乎当它位于Calling context的信使区域下方时会显得更加对称。之所以会有这样的误解,是因为我们对Server、Client的理解有了偏差。你应该记住除了信使区域是位于Calling context外,另外三个区域都是处在Target Context。而所谓的Server,Client是针对处在Target Context中的对象在某不同时刻所扮演不同角色而言的。当然Calling context中也会有客户端区域,不过其中的MessageSink不是通过Target context的属性注入的,而应该依靠Calling context中的上下文属性注入。


下载清晰版本

上图说明了区域的概念。目标上下文包含名为OBJ1和OBJ2的两个对象。我们选择在目标上下文中放置两个对象而不是一个是为了更好地说明对象区域和信使区域是在对象层面与消息的拦截关联起来的,而服务器区域和客户端区域则是在上下文层面与消息的拦截关联起来的。

我们在每个区域中放置了两个自定义消息接收器是为了更好地说明一个区域能包含零个、一个或多个消息接收器。具体地说,所有自定义消息接收器都通过目标上下文的属性注入区域,即使这个区域不属于目标上下文。因为你可以定义你自己的上下文属性类,你可以选择必须注入哪个消息接收器。

你可能注意到每个区域都包含一个用于通知CLR退出区域的系统终结器接收器(system terminator sink),它是由Remoting框架定义的,并且总是位于每个区域的末尾。

当调用方上下文和目标上下文处在同一个应用程序域中时,CLR会使用mscorlib.dll中CrossContextChannel内部类的实例作为信道。这个实例会使得当前线程的Context属性发生切换。图中也展示了这一实例。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值