2.3.4 消息筛选
在上面的案例演示中,提到了两个特殊的对象ChannelDispatcher和ChannelListener。这两个对象在整个WCF的消息分发系统中具有重要的地位,在这节里,我们对WCF的整个消息分发过程作一个简单的介绍。
连接请求的监听
当服务被成功寄宿时,WCF在服务端创建一个或多个监听器,用于服务调用请求的监听。举个例子,我们为服务CalculatorService添加三个基于BasicHttpBinding的终结点,它们分别具有如下3个终结点地址(逻辑地址)。
● http://127.0.0.1:9999/CalculatorService
● http://127.0.0.1:8888/CalculatorService
● http://127.0.0.1:7777/CalculatorService
我们为前两个终结点指定一个不同于终结点地址的监听地址(物理地址):http://127.0.0.1:6666/CalculatorService。第三个终结点的监听地址(物理地址)与终结点地址(逻辑地址)共享相同的URI:http://127.0.0.1:7777/CalculatorService。相应的配置如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="Artech.WcfServices.Services.CalculatorService"> <endpoint address="http://127.0.0.1:9999/CalculatorService" binding="basicHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" listenUri="http://127.0.0.1:6666/CalculatorService" /> <endpoint address="http://127.0.0.1:8888/CalculatorService" binding="basicHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" listenUri="http://127.0.0.1:6666/CalculatorService" /> <endpoint address="http://127.0.0.1:7777/CalculatorService" binding="basicHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" /> </service> </services> </system.serviceModel> </configuration>
现在的情况是,1个服务、3个终结点、2个监听地址。当服务被成功寄宿时,WCF会创建两个信道分发器(ChannelDispatcher)对象,每个信道分发器各拥有属于自己的信道监听器(ChannelListener),它们分别绑定到两个监听地址对应的端口进行服务调用请求的监听。此外,WCF还会为服务的3个终结点创建3个终结点分发器(EndpointDispatcher),当信道分发器通过信道监听器接收到消息时,将会根据消息自身携带的信息选择与之相匹配的终结点分发器,根据消息进行终结点分发器的选择机制称为消息筛选(Message Filter)。关于上面介绍的场景,图2-9体现了基于CalculatorService对应的ServiceHost、ChannelDispatcher和EndpointDispatcher之间的关系。
图2-9 ServiceHost-ChannelDispatcher-EndpointDispatcher
图2-9揭示的围绕CalculatorService的各个相关对象之间的关系也可以从下面的程序得以印证。
using System.ServiceModel; using Artech.WcfServices.Services; using System.Threading; using System; using System.ServiceModel.Dispatcher; namespace Artech.WcfServices.Hosting { class Program
{ static void Main(string[] args) { using (ServiceHost serviceHost = new ServiceHost (typeof(CalculatorService))) { serviceHost.Open(); int i=0; foreach (ChannelDispatcher channelDispatcher in serviceHost. ChannelDispatchers) { Console.WriteLine("ChannelDispatcher {0}: ListenUri: {1}", ++i, channelDispatcher.Listener.Uri); int j = 0; foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { Console.WriteLine("\tEndpointDispatcher {0}: EndpointAddress: {1}", ++j,endpointDispatcher. EndpointAddress.Uri); } } Console.Read(); } } } }
输出结果:
ChannelDispatcher 1: ListenUri: http://127.0.0.1:6666/CalculatorService EndpointDispatcher 1: EndpointAddress: http://127.0.0.1:9999/ CalculatorService EndpointDispatcher 2: EndpointAddress: http://127.0.0.1:8888/ CalculatorService ChannelDispatcher 2: ListenUri: http://127.0.0.1:7777/CalculatorService EndpointDispatcher 1: EndpointAddress: http://127.0.0.1:7777/ CalculatorService
EndpointDispatcher的选择和消息的分发
在WCF整个消息监听与分发体系中,信道分发器和终结点分发器是两个核心的对象。信道分发器进行请求监听和消息接收,终结点分发器最终完成对消息的处理。信道分发器接收的消息最终需要分发给相应的终结点分发器,而消息筛选解决了对终结点的选择问题。消息筛选依赖于终结点分发器两个重要的对象:AddressFilter和Contractfilter,它们分别实现基于终结点地址和服务契约的消息筛选。
public class EndpointDispatcher { //其他成员 public MessageFilter AddressFilter { get; set; } public MessageFilter ContractFilter { get; set; } }
AddressFilter和Contractfilter具有相同的类型:System.ServiceModel.Dispatcher. MessageFilter。MessageFilter是一个抽象类型,定义了两个重载的Match方法,用以判断持有该MessageFilter的EndpointDispatcher是否和接收的消息相匹配。
public abstract class MessageFilter { public abstract bool Match(Message message); public abstract bool Match(MessageBuffer buffer); }
当ChannelDispatcher接收到请求消息时,会遍历属于自己的EndpointDispatcher列表,获取它们的两个MessageFilter:AddressFilter和Contractfilter,并将消息对象传入Match方法。如果返回值均为true,则意味着相应的EndpointDispatcher为真正的目标终结点。
WCF定义了以下6种不同的MessageFilter类型,它们均直接继承自MessageFilter抽象类型。
● ActionMessageFilter:每一个服务操作具有一个Action属性,通过OperationContractAttribute特性进行定义。一个服务契约包含一个或多个服务操作,所以一个终结点具有一组Action列表。AddressMessageFilter通过判断SOAP消息的Action报头的值是否在终结点Action列表之中,从而选择正确的终结点;
● EndpointAddressMessageFilter:EndpointAddress是一个终结点不可或缺的元素,EndpointAddress不仅包含服务的地址,也包含寻址的报头(AddressHeader),能够通过EndpointAddressMessageFilter筛选的终结点需要同时满足两个要求:终结点地址URI要与SOAP的To报头值一致;SOAP消息具一致的报头信息;
● XPathMessageFilter:SOAP消息也是一个XML,所以可以根据一个具体的XPath表达式和SOAP的内容进行匹配;
● PrefixEndpointAddressMessageFilter:和EndpointAddressMessageFilter筛选机制类似,不同的是PrefixEndpointAddressMessageFilter采用“最长前缀匹配”机制。比如,终结点地址指定的URI为http://www.artech.com/Foo,而请求消息的To报头的URI为http://www.artech.com/Foo/Bar,这样可以被认为是匹配的;
● MatchAllMessageFilter:不管消息的内容是什么,都会匹配成功;
● MatchNoneMessageFilter:和MatchAllMessageFilter相反,不管消息的内容是什么,都不会匹配成功。
在默认情况下,EndpointDispatcher的AddressFilter和ContractFilter分别采用EndpointAddressMessageFilter和ActionMessageFilter。如果希望使用其他的值,可以通过自定义Behavior的形式覆盖默认的值。对于AddressFilter,最直接的方式就是通过ServiceBehaviorAttribute的AddressFilterMode属性指定你所需要的MessageFilter模式。
public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior { //其他成员 public AddressFilterMode AddressFilterMode { get; set; } }
public enum AddressFilterMode { Exact, Prefix, Any }
AddressFilterMode中的3个枚举值(Exact、Prefix和Any)对应的MessageFilter分别为:EndpointAddressMessageFilter、PrefixEndpointAddressMessageFilter和MatchAllMessageFilter。下面的代码将AddressFilter指定为MatchAllMessageFilter。
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)] public class CalculatorService:ICalculator { //省略成员 }