冠军

导航

Advanced .NET Remoting: 第 9 章 3.在 Remoting 中传递额外的运行时信息

Advanced .NET Remoting:第 9 章

3.传递运行时信息

前面使用的接收器 ( Sink ) 是 IClientChannelSinks 与 IServerChannelSinks。这意味着它们工作在格式化器已经序列化 IMessage 对象 之后 。而对于 IMessageSink 来说,不同的是,可以直接工作在消息的内容被格式化 之前 。这意味着,你对 IMessage 内容的任何修改都将被序列化,进而反映到最终的流中。

注意:即使你试图在 IClienntChannelSink 中修改 IMessage 对象的内容,需要注意的是,这些修改 不会 被传播到服务器端,因为序列化的流已经通过 IMessage 对象生成了。

基于该区别,客户端 IMessageSink 可以用来从客户端将运行时信息传递到服务器端。在随后的示例中,我将向您展示如何将客户端的线程 ( thread ) 的当前优先级别 ( priority ) 传递到服务器端,以便远程方法可以运行在同样的优先级上。

为了从客户端向服务器端发送任意数据,你需要将它加入到 Message 对象的逻辑调用上下文 ( logical call context ) 中。通过这种方式,你可以传递可序列化对象 ( serializable ) 或者扩展了 MarshalByRefObject 的对象。例如,为了在对服务器上任何方法的调用中,传递客户端的线程的当前上下文,你可以实现如下的 SyncProcessMessage() 方法:

public IMessage SyncProcessMessage(IMessage msg)
{
   if (msg as IMethodCallMessage != null)
   {
      LogicalCallContext lcc =
         (LogicalCallContext) msg.Properties["__CallContext"];

      lcc.SetData("priority",Thread.CurrentThread.Priority);
      return _nextMsgSink.SyncProcessMessage(msg);
   }
   else
   {
      return _nextMsgSink.SyncProcessMessage(msg);
   }
}

对于 AsyncProcessMessage() 方法也同样处理。

public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
{
   if (msg as IMethodCallMessage != null)
   {
      LogicalCallContext lcc =
         (LogicalCallContext) msg.Properties["__CallContext"];

      lcc.SetData("priority",Thread.CurrentThread.Priority);
      return _nextMsgSink.AsyncProcessMessage(msg,replySink);
   }
   else
   {
      return _nextMsgSink.AsyncProcessMessage(msg,replySink);
   }
}

在服务器端,你也必须实现一个 IServerChannelSink 来从 IMessage 对象中提取调用上下文信息 ( call context ),然后设置到 Thread.CurrentThread.Priority 上来应用该值。

public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack,
   IMessage requestMsg,
   ITransportHeaders requestHeaders,
   Stream requestStream,
   out IMessage responseMsg,
   out ITransportHeaders responseHeaders,
   out Stream responseStream)
{
   LogicalCallContext lcc =
      (LogicalCallContext) requestMsg.Properties["__CallContext"];

   // storing the current priority
   ThreadPriority oldprio = Thread.CurrentThread.Priority;

   // check if the logical call context contains "priority"
   if (lcc != null && lcc.GetData("priority") != null)
   {
      // fetch the priority from the call context
      ThreadPriority priority =
         (ThreadPriority) lcc.GetData("priority");

      Console.WriteLine(" -> Pre-execution priority change {0} to {1}",
         oldprio.ToString(),priority.ToString());

      // set the priority
      Thread.CurrentThread.Priority = priority;
   }

   // push on the stack and pass the call to the next sink
   // the old priority will be used as "state" for the response
   sinkStack.Push(this,oldprio);
   ServerProcessing spres = _next.ProcessMessage (sinkStack,
      requestMsg, requestHeaders, requestStream,
      out responseMsg,out responseHeaders,out responseStream);

   // restore priority if call is not asynchronous
   if (spres != ServerProcessing.Async)
   {
      if (lcc != null && lcc.GetData("priority") != null)
      {
         Console.WriteLine(" -> Post-execution change back to {0}",oldprio);
         Thread.CurrentThread.Priority = oldprio;
      }
   }  
   return spres;
}

用于服务器端的接收器的接收器提供器非常简单。它看起来多少与前面的 IServerChannelSink 是相同的。

在客户端,这种方式有一点不太方便。请记住,你现在是在实现 IMessageSink 而不是 IClientChannelSing。如果寻找 IMessageSinkProvider 的话不会有 任何结果,所以,此时你还是不得不实现一个 IClientChannelSink - 即使实际上该连接器是 IMessageSink。当查看随后部分的 IClientChannelSinkProvider 接口的时候,就会发现这个问题。

IClientChannelSink CreateSink(IChannelSender channel,
   string url,
   object remoteChannelData);

这个接口说明,在任何场景下,CreateSink() 都只会返回 IClientChannelSink,即使你的连接器只需要实现 IMessageSink。所以现在你不得不扩展你的 IMessageSink 同时去实现 IClientChannelSink。你还不得不使用条件判断,因为 IClientChannelSink 也定义来更多的方法需要实现。这些方法将在这个连接器作为通道连接器 ( channel sink ) 的时候而不是作为消息连接器 ( 也就是说,在格式化器之后 ) 被调用。 你可能不希望你的用户将该连接器放置在格式化器 之后 ( 它在这里并不有效,因为它修改了 IMessage 对象的内容 ),所以你会希望对于这些方法抛出异常。

完整的客户端 PriorityEmitterSink 代码,会在用于错误的顺序时抛出这些异常,如列表 13-12 所示:

using System;
using System.Collections;
using System.IO;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;
using System.Threading;
namespace PrioritySinks
{
    public class PriorityEmitterSink : BaseChannelObjectWithProperties,
        IClientChannelSink, IMessageSink
    {
        private IMessageSink _nextMsgSink;
        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink     replySink)
        {
            // only for method calls
            if (msg as IMethodCallMessage != null)
            {
                LogicalCallContext lcc =
                    (LogicalCallContext) msg.Properties["__CallContext"];
                lcc.SetData("priority",Thread.CurrentThread.Priority);
                return _nextMsgSink.AsyncProcessMessage(msg,replySink);
            }
            else
            {
                return _nextMsgSink.AsyncProcessMessage(msg,replySink);
            }
        }

        public IMessage SyncProcessMessage(IMessage msg)
        {
            // only for method calls
            if (msg as IMethodCallMessage != null)
            {
                LogicalCallContext lcc =
                (LogicalCallContext) msg.Properties["__CallContext"];
                lcc.SetData("priority",Thread.CurrentThread.Priority);
                return _nextMsgSink.SyncProcessMessage(msg);
            }
            else
            {
                return _nextMsgSink.SyncProcessMessage(msg);
            }
        }

        public PriorityEmitterSink (object next)
        {
            if (next as IMessageSink != null)
            {
                _nextMsgSink = (IMessageSink) next;
            }
        }

        public IMessageSink NextSink
        {
            get
            {
                return _nextMsgSink;
            }
        }

        public IClientChannelSink NextChannelSink
        {
            get
            {
                throw new RemotingException("Wrong sequence.");
            }
        
        }

        public void AsyncProcessRequest(IClientChannelSinkStack sinkStack,
            IMessage msg,
            ITransportHeaders headers,
            Stream stream)
        {
            throw new RemotingException("Wrong sequence.");
        }

        public void AsyncProcessRequest(IClientChannelSinkStack sinkStack,
            IMessage msg,
            ITransportHeaders headers,
            Stream stream)
        {
            throw new RemotingException("Wrong sequence.");
        }

        public void AsyncProcessResponse(
            IClientResponseChannelSinkStack sinkStack,
            object state,
            ITransportHeaders headers,
            Stream stream)
        {
            throw new RemotingException("Wrong sequence.");
        }

        public System.IO.Stream GetRequestStream(IMessage msg,
            ITransportHeaders headers)
        {
            throw new RemotingException("Wrong sequence.");
        }

        public void ProcessMessage(IMessage msg,
            ITransportHeaders requestHeaders,
            Stream requestStream,
            out ITransportHeaders responseHeaders,
            out Stream responseStream)
        {
            throw new RemotingException("Wrong sequence.");
        }
    }
}

客户端的 PriorityEmitterSinkProvider 如列表 13-13 所示,实现很直接。只有方法 CreateSink() 比较值得关注。

列表 13-13 客户端的 PriorityEmitterSinkProvider

using System;
using System.Collections;
using System.Runtime.Remoting.Channels;

namespace PrioritySinks
{
    public class PriorityEmitterSinkProvider: IClientChannelSinkProvider
    {
        private IClientChannelSinkProvider next = null;
        public PriorityEmitterSinkProvider(IDictionary properties,
            ICollection providerData)
        {
            // not needed
        }

        public IClientChannelSink CreateSink(IChannelSender channel,
            string url, object remoteChannelData)
        {
            IClientChannelSink nextsink =
                next.CreateSink(channel,url,remoteChannelData);
            return new PriorityEmitterSink(nextsink);
        }

        public IClientChannelSinkProvider Next
        {
            get { return next; }
            set { next = value; }
        }
    }
}

列表 13-14 是服务器端的 IServerChannelSink 实现,与客户端不同,它不是 IMessageSink,所以该实现更为一致。你不需要在这里实现任何其它接口。

列表 13-14 服务器端的 PriorityChangerSink

using System;
using System.Collections;
using System.IO;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging ;
using System.Runtime.Remoting.Channels;
using System.Threading;
namespace PrioritySinks
{
    public class PriorityChangerSink : BaseChannelObjectWithProperties,
        IServerChannelSink, IChannelSinkBase
    {
        private IServerChannelSink _next;
        public PriorityChangerSink (IServerChannelSink next)
        {
            _next = next;
        }

        public void AsyncProcessResponse (
            IServerResponseChannelSinkStack sinkStack,
            Object state,
            IMessage msg,
            ITransportHeaders headers,
            Stream stream)
        {
            // restore the priority
            ThreadPriority priority = (ThreadPriority) state;
            Console.WriteLine(" -> Post-execution change back to {0}",priority);
            Thread.CurrentThread.Priority = priority;
        }

        public Stream GetResponseStream (IServerResponseChannelSinkStack sinkStack,
            Object state,
            IMessage msg,
            ITransportHeaders headers )
        {
            return null;
        }

        public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack,
            IMessage requestMsg,
            ITransportHeaders requestHeaders,
            Stream requestStream,
            out IMessage responseMsg,
            out ITransportHeaders responseHeaders,
            out Stream responseStream)
        {
            LogicalCallContext lcc =
                (LogicalCallContext) requestMsg.Properties["__CallContext"];
            // storing the current priority
            ThreadPriority oldprio = Thread.CurrentThread.Priority;
            // check if the logical call context contains "priority"
            if (lcc != null && lcc.GetData("priority") != null)
            {
                // fetch the priority from the call context
                ThreadPriority priority =
                    (ThreadPriority) lcc.GetData("priority");
                Console.WriteLine("-> Pre-execution priority change {0} to {1}",
                    oldprio.ToString(),priority.ToString());
                // set the priority
                Thread.CurrentThread.Priority = priority;
            }

            // push on the stack and pass the call to the next sink
            // the old priority will be used as "state" for the response
            sinkStack.Push(this,oldprio);
            ServerProcessing spres = _next.ProcessMessage (sinkStack,
                requestMsg, requestHeaders, requestStream,
                out responseMsg,out responseHeaders,out responseStream);
            
            // restore priority if call is not asynchronous
            if (spres != ServerProcessing.Async)
            {
                if (lcc != null && lcc.GetData("priority") != null)
                {
                    Console.WriteLine("-> Post-execution change back to {0}",oldprio);
                    Thread.CurrentThread.Priority = oldprio;
                }
            }
            return spres;
        }

        public IServerChannelSink NextChannelSink
        {
            get {return _next;}
            set {_next = value;}
        }
    }
}

列表 13-15 是相关的服务器端连接器提供器,它实现了接口 IServerChannelSinkProvider

列表 13-15 服务器端的 PriorityChangerSinkProvider

using System;
using System.Collections;
using System.Runtime.Remoting.Channels;
namespace PrioritySinks
{
    public class PriorityChangerSinkProvider: IServerChannelSinkProvider
    {
        private IServerChannelSinkProvider next = null;
        public PriorityChangerSinkProvider(IDictionary properties,
            ICollection providerData)
        {
            // not needed
        }

        public void GetChannelData (IChannelDataStore channelData)
        {
            // not needed
        }

        public IServerChannelSink CreateSink (IChannelReceiver channel)
        {
            IServerChannelSink nextSink = next.CreateSink(channel);
            return new PriorityChangerSink(nextSink);
        }

        public IServerChannelSinkProvider Next
        {
            get { return next; }
            set { next = value; }
        }
    }
}

为了测试该连接器的组合,使用如下的 SAO (服务器端激活对象) ,它会返回服务器端的当前线程的优先级:

public class TestSAO: MarshalByRefObject
{
    public String getPriority()
    {
        return System.Threading.Thread.CurrentThread.Priority.ToString();
    }
}

这个 SAO 将被客户端使用不同的线程优先级调用多次。服务器端使用的配置文件如下所示:

<configuration>
    <system.runtime.remoting>
        <application>
            <channels>
                <channel ref="http" port="5555">
                    <serverProviders>
                        <formatter ref="soap" />
                        <provider
                            type="PrioritySinks.PriorityChangerSinkProvider, PrioritySinks" />
                    </serverProviders>
                </channel>
            </channels>
            <service>
                <wellknown mode="Singleton"
                    type="Server.TestSAO, Server" objectUri="TestSAO.soap" />
            </service>
        </application>
    </system.runtime.remoting>
</configuration>

客户端的配置文件如下所示:

<configuration>
    <system.runtime.remoting>
        <application>
            <channels>
                <channel ref="http">
                    <clientProviders>
                        <provider
                            type="PrioritySinks.PriorityEmitterSinkProvider, PrioritySinks" />
                        <formatter ref="soap" />
                    </clientProviders>
                </channel>
            </channels>
            <client>
                <wellknown type="Server.TestSAO, generated_meta"
                    url="http://localhost:5555/TestSAO.soap" />
            </client>
        </application>
    </system.runtime.remoting>
</configuration>

在用于测试的客户端,你可以使用 SoapSuds 来抽取元数据。当你运行如列表 13-16 所示的应用程序的时候,将会看到如图 13-8 所示的输出。

using System;
using System.Runtime.Remoting;
using Server; // from generated_meta.dll
using System.Threading;
namespace Client
{
    delegate String getPrioAsync();
    class Client
    {
        static void Main(string[] args)
        {
            String filename = "client.exe.config";
            RemotingConfiguration.Configure(filename);
            TestSAO obj = new TestSAO();
            test(obj);
            
            Thread.CurrentThread.Priority = ThreadPriority.Highest;
            test(obj);
            
            Thread.CurrentThread.Priority = ThreadPriority.Lowest;
            test(obj);
            
            Thread.CurrentThread.Priority = ThreadPriority.Normal;
            test(obj);

            Console.ReadLine();
        }

        static void test(TestSAO obj)
        {
            Console.WriteLine("----------------- START TEST CASE ---------------");
            Console.WriteLine(" Local Priority: {0}",
            Thread.CurrentThread.Priority.ToString());
            String priority1 = obj.getPriority();
            Console.WriteLine(" Remote priority: {0}",priority1.ToString());
            Console.WriteLine("----------------- END TEST CASE ---------------");
        }
    }
}

图 13-8 通过客户端测试的输出,展示了连接器如愿工作

参考资料

posted on 2022-06-01 10:45  冠军  阅读(121)  评论(0编辑  收藏  举报