WCF服务端运行时架构体系详解[上篇]

时间:2022-04-27
本文章向大家介绍WCF服务端运行时架构体系详解[上篇],主要内容包括一、从服务描述(Service Description)谈起、二、服务端架构体系概览、三、终结点分发器选择机制、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

WCF的服务端架构体系又可以成为服务寄宿端架构体系。我们知道,对于一个基于某种类型的服务进行寄宿只需要使用到一个唯一的对象,那就是ServiceHost。甚至在某种语境下,我们所说的服务实际上就是指的对应的ServiceHost对象。整个服务寄宿过程包括两个阶段,即服务描述的创建和服务端运行框架的建立。而第一个阶段创建的服务描述是为了第二个阶段对服务端运行时框架建立服务的,所以我们有必要在对服务描述进行简单的介绍。

目录: 一、从服务描述(Service Description)谈起 二、服务端架构体系概览 三、终结点分发器选择机制

一、从服务描述(Service Description)谈起

当ServiceHost在被实例化的过程中,用于描述整个服务的ServiceDescription对象被创建出来。对于一个服务来说,它的核心包括:一组终结点列表和一组服务行为列表。这可以通过如下所示的ServiceDescription的定义看出来。

   1: public class ServiceDescription
   2: {
   3:     //其他成员
   4:     public KeyedByTypeCollection<IServiceBehavior> Behaviors { get; }
   5:     public ServiceEndpointCollection Endpoints { get; }
   6: }

而对于终结点来说,对于它的ABC三要素,即地址(Address)、绑定(Binding)和契约(Contract)早已了然于胸了。所以用于描述终结点的ServiceEndpoint类型具有Address、Binding和Contract三个核心属性。此外还有基于该终结点的行为列表,通过Behaviors属性表示。ServiceEndpoint的定义如下所示。

   1: public class ServiceEndpoint
   2: {
   3:     //其他成员
   4:     public EndpointAddress Address { get; set; }
   5:     public Binding Binding { get; set; }
   6:     public ContractDescription Contract { get; }    
   7:     public KeyedByTypeCollection<IEndpointBehavior> Behaviors { get; }
   8: }

现在我们进一步分析用以描述服务契约的ContractDescription类型。由于服务契约本质上是一组相关操作的组合,所以ContractDescription的核心属性是如下所示的表示所有操作描述的Operations属性。除了操作描述列表之外,自然还有基于服务契约本身的行为列表。

   1: public class ContractDescription
   2: {
   3:     //其他成员
   4:     public OperationDescriptionCollection Operations { get; }
   5:     public KeyedByTypeCollection<IContractBehavior> Behaviors { get; }
   6: }

至于对服务操作的描述,对应的类型为OperationDescription。OperationDescription中定义了一系列基于服务操作的属性,它们以及在之前的章节有过详细的介绍了,在这里我们主要关注的是用以表示操作行为列表的属性Behaviors。

   1: public class OperationDescription
   2: {  
   3:     //其他成员
   4:     public KeyedByTypeCollection<IOperationBehavior> Behaviors { get; }
   5: }

上述从服务ServiceDescription到ServiceEndpoint,从ServiceEndpoint到ContractDescription,最终到OperationDescription的层次结构基本上可以通过下图来表示。

在构建ServiceHost过程中创建的用于描述整个服务的ServiceDescription对象,最终成为了构建服务端运行时架构体系的基础。而该架构体系在ServiceHost开启的过程中被构建出来,这也是为什么在ServiceHost开启之后对服务描述所作的任何该表都是无效的根本原因。

二、服务端架构体系概览

为了让读者对服务端运行时架构体系的结构具有更加深刻的认识,我们针对一个具体的服务寄宿应用场景来进行介绍。假设我们采用如下的配置对服务CalculatorService进行寄宿。通过这段配置,三个基于WSHttpBinding的终结点被添加。

   1: <configuration>
   2:   <system.serviceModel>
   3:     <services>
   4:       <service name="Artech.WcfServices.CalculatorService">
   5:         <endpoint address      = "http://127.0.0.1:7777/CalculatorService"
   6:                   binding      = "wsHttpBinding"
   7:                   contract     = "Artech.WcfServices.ICalculator"
   8:                   listenUri    = "http://127.0.0.1:6666/CalculatorService"
   9:                   listenUriMode= "Explicit"/>
  10:         <endpoint address      = "http://127.0.0.1:8888/CalculatorService"
  11:                   binding      = "wsHttpBinding"
  12:                   contract     = "Artech.WcfServices.ICalculator"
  13:                   listenUri    = "http://127.0.0.1:6666/CalculatorService"
  14:                   listenUriMode= "Explicit"/>
  15:         <endpoint address      = "http://127.0.0.1:9999/CalculatorService"
  16:                   binding      =  "wsHttpBinding"
  17:                   contract     = "Artech.WcfServices.ICalculator"
  18:                   listenUri    = "http://127.0.0.1:6666/CalculatorService"
  19:                   listenUriMode= "Unique"/>
  20:       </service>
  21:     </services>
  22:   </system.serviceModel>
  23: </configuration>

如上面的配置片断所示,虽然这三个终结点具有不同的地址,但是它们却使用了相同的监听URI(通过listenUri属性设置)。进一步地,虽然三个终结点具有相同的监听URI,但是它们的监听URI模式(通过listenUriMode属性设置),前两个终结点为Explicit,而第三个为Unique。按照在《WCF技术剖析(卷1)》第三章介绍的关于“物理地址和逻辑地址”原理,你会知道在这种情况下,最终的监听地址具有两个:http://127.0.0.1:6666/CalculatorService和http://127.0.0.1:6666/CalculatorService/<<guid>>。

当基于上面配置创建的ServiceHost在正常开启后,WCF会创建如下图所示的架构体系。首先通过调用绑定的BuildChannelListener方法创建信道监听器(实际上是多个信道监听器构成的信道监听器栈,最终返回的是最上层的信道监听器。如果读者对于信道层的相关内容不是特别了解,请参考《WCF技术剖析(卷1)》第3章《绑定与信道栈》)。这两个信道监听器分别绑定到上述的两个监听地址进行请求消息的监听。

针对这两个信道监听器,WCF会创建相应的信道分发器(ChannelDispatcher)对象。而针对在配置中定义的三个终结点,它们则分别对应着一个终结点分发器(EndpointDispatcher)。每个终结点分发器分发器都具有各自的运行时,被称为分发运行时(DispatchRuntime)。

当信道监听器成功监听到抵达的请求消息,它会利用创建的信道栈对消息进行接收和处理。经过信道栈处理过的消息通过信道监听器所在的信道分发器转发给相应的终结点分发器。终结点最终将接收到的消息在自己的分发运行时中进行处理。而处理后的结果被封装在创建的回复消息中回传给信道分发器,并最终通过信道栈返回给客户端。那么现在有一个问题:信道监听器在接收到经过信道栈接收和处理的消息后,如果判断需要将消息转发给哪个终结点分发器呢?这就是涉及到终结点分发器的选择机制。

三、终结点分发器选择机制

我们将注意力再次返回到上图。你会发现除了分发运行时,每个终结点分发器还具有两个重要的对象:地址筛选器(AddressFilter)和契约筛选器(ContractFilter)。它们都是属于一个叫做消息筛选器(MessageFilter)的对象。信道分发器就是通过这两个消息筛选器最终决定所在的终结点分发器是否适合处理当前请求消息。

具体来说,每个消息筛选器均继承自Dispatcher.MessageFilter这个抽象类。MessageFilter具有两个重载的分别以Message和MessageBuffer作为参数的方法。信道分发器在决定应该将接收的消息路由给哪个终结点分发器之前,会将基于路由消息的Message或者MessageBuffer对象作为输入参数,调用所有终结点分发器两个消息筛选器的Match方法。如果方法方法返回True,则表明该终结点分发器与需要路有的消息匹配。

   1: public abstract class MessageFilter
   2: {    
   3:     public abstract bool Match(Message message);
   4:     public abstract bool Match(MessageBuffer buffer);
   5: }

终结点分发器在WCF的应用编程接口中通过类型System.ServiceModel.Dispatcher.EndpointDispatcher表示。EndpointDispatcher的部分定义如下面的代码片断所示,除了代表上述两个消息筛选器的两个属性AddressFilter和ContractFilter之外,还有一个额外的整型的FilterPriority属性。FilterPriority属性表示筛选的优先级,当两个以上终结点分发器同时与路由的消息匹配的情况下,由优先级最高的终结点分发器会被选用。代表FilterPriority的数据越大,意味着优先级越高。如果同时有两个或者以上具有最高筛选优先级的终结终结点分发器,系统会抛出一个MultipleFilterMatchesException异常。

   1: public class EndpointDispatcher
   2: {
   3:     //其他成员
   4:     public MessageFilter AddressFilter { get; set; }
   5:     public MessageFilter ContractFilter { get; set; }
   6:     public int FilterPriority { get; set; }
   7: }

为了满足各种消息理由的需要,WCF为我们定义了如下六种典型的消息消息筛选器。如果这6种消息筛选器依然不能满足你的需求,你可以通过继承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模式。该属性的类型为AddressFilterMode枚举。它具有三个枚举值(Exact、Prefix和Any)对应于EndpointAddressMessageFilter、PrefixEndpointAddressMessageFilter和 MatchAllMessageFilter这三种消息筛选器。

   1: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
   2: {
   3:     //其他成员
   4:     public AddressFilterMode AddressFilterMode { get; set; }
   5: }
   6: public enum AddressFilterMode
   7: {
   8:     Exact,
   9:     Prefix,
  10:     Any
  11:  } 

WCF服务端运行时架构体系详解[上篇] WCF服务端运行时架构体系详解[中篇] WCF服务端运行时架构体系详解[下篇] WCF服务端运行时架构体系详解[续篇]