WCF后续之旅(16): 消息是如何分发到Endpoint的--消息筛选(Message Filter)
在介绍终结点的ListenUriMode时,我们提到了两个特殊的对象ChannelDispatcher和ChannelListener。这两个对象在整个WCF的消息分发系统中具有重要的地位,在这节里,我们对WCF的整个消息分发过程作一个简单的介绍。
一、连接请求的监听
当我们通过ServiceHost对某个服务进行寄宿的时候,实际上WCF是在为我们创建一个监听器,并监听来自外界的服务访问请求。我们举一个例子,比如针对服务CalculateService,具有如下的配置:该服务具有基于BasicHttpBinding的三个终结点,他们的地址(逻辑地址)分别为:http://127.0.0.1:9999/calculateservice,http://127.0.0.1:8888/calculateservice和http://127.0.0.1:7777/calculateservice,而前两个共享同一个ListenUri——http://127.0.0.1:6666/calculateservice。而第三个使用默认的ListenUri(也就是终结点地址)。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <services>
5: <service name="Artech.WcfServices.Services.CalculateService">
6: <endpoint address="http://127.0.0.1:9999/calculateservice" binding="basicHttpBinding"
7: contract="Artech.WcfServices.Contracts.ICalculate" listenUri="http://127.0.0.1:6666/calculateservice" />
8: <endpoint address="http://127.0.0.1:8888/calculateservice" binding="basicHttpBinding"
9: contract="Artech.WcfServices.Contracts.ICalculate" listenUri="http://127.0.0.1:6666/calculateservice" />
10: <endpoint address="http://127.0.0.1:7777/calculateservice" binding="basicHttpBinding"
11: contract="Artech.WcfServices.Contracts.ICalculate" />
12: </service>
13: </services>
14: </system.serviceModel>
15: </configuration>
16:
当我们通过ServiceHost对该服务进行寄宿的时候,会为该服务创建一个ServiceHost对象。当我们执行ServiceHost的Open方法的时候,WCF会创建两个ChannelDispatcher对象。为什么会是两个ChannelDispatcher对象呢?这是因为ChannelDispatcher是根据实际的监听地址创建的,在本例中,虽然我们为服务创建了三个终结点,由于前两个共享同一个监听地址,所所以针对于服务的ServiceHost对象,具有两个ChannelDispatcher对象与之匹配。对于每个ChannelDispatcher对象而言,他们各自对应一个唯一的ChannelListener对象,ChannelListener具有两个方面的作用,其一是绑定到一个具体的URI,监听来自外界的连接请求,其二就是当检测到请求后,创建信道堆栈(channel stack)接受、处理请求。ServiceHost的Open方法的执行,同时也预示着ChannelListener监听工作的开始。
由于我们为该服务注册了三个终结点,WCF还会创建3个EndpointDispatcher对象,分别于三个终结点对应。对于服务访问请求的消息,会先被对应的ChannelDispacher(这取决于该消息是从哪个ChannelListener接收到的)接收,ChannelDispacher本身并不会对该消息进行处理,而是为将它转发到对应的EndpointDispatcher上,基于该消息的所有后续处理都叫由EndpointDispatcher进行处理。对于这三个EndpointDispatcher对象,前面两个和第一个ChannelDispatcher匹配(根据实际的监听地址进行匹配)。
总结一下,一个CalculateService服务,对应着一个ServiceHost对象。该ServiceHost对象有具有两个ChannelDispatcher对象,这两个ChannelDispatcher各自具有一个ChannelListener对象,他们对应的监听地址分别为http://127.0.0.1:6666/calculateservice和http://127.0.0.1:7777/calculateservice。对于前一个ChannelDispatcher,具有两个与之匹配的EndpointDispatcher对象,后一个具有一个匹配的EndpointDispatcher对象。具体关系如下图所示:
我们可以通过一个例子在正式这一点,完全针对我们上面提供的配置,我们通过下面的代码将该服务在一个控制台应用中进行寄宿,然后打印出ChannelDispatcher和EndpointDispatcher的关系:
1: //---------------------------------------------------------------
2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
3: //---------------------------------------------------------------
4: using System.ServiceModel;
5: using Artech.WcfServices.Services;
6: using System.Threading;
7: using System;
8: using System.ServiceModel.Dispatcher;
9:
10: namespace Artech.WcfServices.Hosting
11: {
12: class Program
13: {
14: static void Main(string[] args)
15: {
16: using (ServiceHost serviceHost = new ServiceHost(typeof(CalculateService)))
17: {
18: serviceHost.Open();
19: int i=0;
20:
21: foreach (ChannelDispatcher channelDispatcher in serviceHost.ChannelDispatchers)
22: {
23: Console.WriteLine("ChannelDispatcher {0}: ListenUri: {1}", ++i, channelDispatcher.Listener.Uri);
24: int j = 0;
25: foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
26: {
27: Console.WriteLine("\tEndpointDispatcher {0}: EndpointAddress: {1}", ++j,endpointDispatcher.EndpointAddress.Uri);
28: }
29: }
30: Console.Read();
31: }
32: }
33: }
34: }
35:
最终输出的结果印证了我们上面对ServiceHost、ChannelDispatcher、ChannelListener和EndpointDispatcher的关系:
1: ChannelDispatcher 1: ListenUri: http://127.0.0.1:6666/calculateservice
2: EndpointDispatcher 1: EndpointAddress: http://127.0.0.1:9999/calculateservice
3: EndpointDispatcher 2: EndpointAddress: http://127.0.0.1:8888/calculateservice
4: ChannelDispatcher 2: ListenUri: http://127.0.0.1:7777/calculateservice
5: EndpointDispatcher 1: EndpointAddress: http://127.0.0.1:7777/calculateservice
2、EndpointDispatcher的选择和消息的分发
接着上面的例子,当服务被成功寄宿之后,两个ChannelDispatcher的ChannelListener便开始在各自的监听URI上进行监听。一旦某个服务调用请求被某个ChannelListener监测到,ChannelListner会调用AcceptChannel方法创建信道栈(channel stack)接收和处理请求消息。
当消息被接收信道栈处理完毕之后,ChannelListener所在的ChannelDispatcher需要将消息分发给对应的EndpointDispatcher。但是对于一个ChannelDiaptcher对应多个EndpointDispatcher的情况,究竟该如何选择适合的EndpointDispatcher呢?EndpointDispatcher的选择依赖于两个特殊的MessageFilter——AddressFilter和ContractFilter。
1: //---------------------------------------------------------------
2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
3: //---------------------------------------------------------------
4: public class EndpointDispatcher
5: {
6: //... ...
7: public MessageFilter AddressFilter { get; set; }
8: public MessageFilter ContractFilter { get; set; }
9: }
10:
我们先来看看MessageFilter的定义, 如下所示,MessageFilter类定义两个重载的Match方法,参数分别是Message和MessageBuffer。当MessageFilter的Match方法返回True,就表明该MessageFilter对象对应的EndpointDispatcher正是真正被请求的EndpointDispatcher。也就是说当ChannelDispatcher进行筛选的时候,会遍历它所有的EndpointDispatcher,获取他们的AddressFilter和ContractFilter,调用Match方法,如果两者都返回true,则表明是真正的需要的EndpointDispatcher。
1: //---------------------------------------------------------------
2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
3: //---------------------------------------------------------------
4: public abstract class MessageFilter
5: {
6: public abstract bool Match(Message message);
7: public abstract bool Match(MessageBuffer buffer);
8: }
9:
WCF定义了6种MessageFilter:ActionMessageFilter、EndpointAddressMessageFilter、XPathMessageFilter、PrefixEndpointAddressMessageFilter、MatchAllMessageFilter和MatchNoneMessageFilter。如下面的类图所示,这6种MessageFilter均继承自抽象类:System.ServiceModel.Dispatcher.MessageFilter。
- ActionMessageFilter:对于服务契约的每个操作都具有一个Action,可以是显示指定的,也可以是默认的(服务契约的命名空间+操作名称),也就是说一个终结点的具有一个Action列表。在进行筛选的时候,如果SOAP消息的Action报头的值存在于终结点的Action列表中,则匹配成功
- EndpointAddressMessageFilter:如果SOAP消息的To报头和终结点的地址完全一样,则匹配成功
- XPathMessageFilter:SOAP消息也是一个XML,所以可以根据一个具体的XPath表达式和SOAP的内容进行匹配
- PrefixEndpointAddressMessageFilter:和EndpointAddressMessageFilter一样,也是通过SOAP消息的To报头和终结点的地址进行比较,不过这里仅仅比较地址的前缀
- MatchAllMessageFilter:不管消息的内容是什么,都会匹配成功
- MatchNoneMessageFilter:和MatchAllMessageFilter相反,不管消息的内容是什么,都不会匹配成功
在默认的情况下,EndpointDispatcher的AddressFilter采用的是EndpointAddressMessageFilter,而ContractFilter采用的是ActionMessageFilter。如果希望改变EndpointDispatcher的AddressFilter和ContractFilter的值,你可以通过自定义Behavior的形式覆盖掉默认的值。对于AddressFilter,你有一种最直接的方式,通过ServiceBehaviorAttribute的AddressFilterMode属性指定你所需要的MessageFilter。
1: //---------------------------------------------------------------
2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
3: //---------------------------------------------------------------
4: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
5: {
6: //... ...
7: public AddressFilterMode AddressFilterMode { get; set; }
8: }
9: public enum AddressFilterMode
10: {
11: Exact,
12: Prefix,
13: Any
14: }
其中Exact对应EndpointAddressMessageFilter;Prefix对应PrefixEndpointAddressMessageFilter;Any对应MatchAllMessageFilter。比如通过下面的代码,将AddressFilter指定为MatchAllMessageFilter:
1: //---------------------------------------------------------------
2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
3: //---------------------------------------------------------------
4: [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
5: public class CalculateService:ICalculate
6: {
7: ...
8: }
WCF后续之旅:
WCF后续之旅(1): WCF是如何通过Binding进行通信的
WCF后续之旅(2): 如何对Channel Layer进行扩展——创建自定义Channel
WCF后续之旅(3): WCF Service Mode Layer 的中枢—Dispatcher
WCF后续之旅(4):WCF Extension Point 概览
WCF后续之旅(5): 通过WCF Extension实现Localization
WCF后续之旅(6): 通过WCF Extension实现Context信息的传递
WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成
WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成
WCF后续之旅(9):通过WCF的双向通信实现Session管理[Part I]
WCF后续之旅(9): 通过WCF双向通信实现Session管理[Part II]
WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance
WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity)
WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响
WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇]
WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇]
WCF后续之旅(14):TCP端口共享
WCF后续之旅(15): 逻辑地址和物理地址
WCF后续之旅(16): 消息是如何分发到Endpoint的--消息筛选(Message Filter)
WCF后续之旅(17):通过tcpTracer进行消息的路由
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。