阅读内容 

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

[日期:2008-09-28] 来源:  作者: [字体: ]
    
  在上一篇文章我 们已经介绍到通过在配置文件中指定自定义的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中加入额外的属性,如下所示:
  
   1: public class CustomMessageSink:IMessageSink
   2: {
   3: public IMessage SyncProcessMessage( IMessage msg )
   4: {
   5: // Add some custom data into msg.
   6: ((IMethodMessage)msg).LogicalCallContext.SetData("MyName" , "idior" );
   7: return m_NextSink.SyncProcessMessage( msg );
   8: }
   9: }
  
  代码 1
  
  在上一篇文章的图2中我们可以看到IClientChannelSinkProvider是通过下面这个方法创建ChannelSink。
  
   1: public IClientChannelSink CreateSink(IChannelSender channel, string url,
   2: object remoteChannelData) {...}
  
  代码 2
  
  注意它的返回值是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创建出来并链接在一起。
  
  1: internal virtual IMessage InternalInvoke(IMethodCallMessage reqMcmMsg,
   2: bool useDispatchMessage, int callType)
  
   3: {
   4: //...
   5: if (identity.ChannelSink == null)
   6: {
   7: IMessageSink envoySink = null;
   8: IMessageSink channelSink = null;
   9: if (!identity.ObjectRef.IsObjRefLite())
   10: {
   11: RemotingServices.CreateEnvoyAndChannelSinks(null,identity.ObjectRef,
   12: out envoySink , out channelSink );
   13: }
   14: else
   15: {
   16: RemotingServices.CreateEnvoyAndChannelSinks(identity.ObjURI, null,
   17: out envoySink , out channelSink );
   18: }
   19: RemotingServices.SetEnvoyAndChannelSinks(identity, envoySink,channelSink );
   20: if (identity.ChannelSink == null)
   21: {
   22: throw new RemotingException("..."));
   23: }
   24: }
   25: //...
   26: }
  
  代码 3
  
  第一个判断语句(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接口中的方法时,全部抛出异常。如下所示:
  
   1: public class SoapClientFormatterSink :IMessageSink, IClientChannelSink//...
   2: {
   3: //...
   4:
   5: //Implement method in IMessageSink
   6: public IMessage SyncProcessMessage(IMessage msg)
   7: {
   8: IMethodCallMessage message1 = (IMethodCallMessage) msg;
   9: try
   10: {
   11: ITransportHeaders headers1;
   12: Stream stream1;
   13: Stream stream2;
   14: ITransportHeaders headers2;
   15: this.SerializeMessage(message1, out headers1, out stream1);
   16: this._nextSink.ProcessMessage(msg, headers1, stream1,
   17: out headers2, out stream2);
   18: if (headers2 == null)
   19: {
   20: throw new ArgumentNullException("returnHeaders");
   21: }
   22: return this.DeserializeMessage(message1, headers2, stream2);
   23: }
   24: catch (Exception exception1)
   25: {
   26: return new ReturnMessage(exception1, message1);
   27: }
   28: catch
   29: {
   30: return new ReturnMessage(new Exception("...")), message1);
   31: }
   32: }
   33:
   34: //Implement method in IClientChannelSink
   35: public void ProcessMessage(...)
   36: {
   37: throw new NotSupportedException();
   38: }
   39:
   40: //...
   41: }
  
  代码 4
  
  然后在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,...);
  
  代码 5
  
  而我们在上图中可以发现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框架。
  
  下一节将介绍当Client和Server对象处在同一个Appdomain时,如何拦截并修改消息,其中将涉及到更多类型的Sink。
    
阅读:
录入:blue1000

推荐 】 【 打印
相关新闻      
本文评论       全部评论
发表评论
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款


点评: 字数
姓名:
Advertisement
内容查询


Advertisement