WCF服務端運行時架構體系詳解[上篇]
WCF的服務端架構體系又可以成為服務寄宿端架構體系。我們知道,對于一個基于某種類型的服務進行寄宿只需要使用到一個唯一的對象,那就是ServiceHost。甚至在某種語境下,我們所說的服務實際上就是指的對應的ServiceHost對象。整個服務寄宿過程包括兩個階段,即服務描述的創建和服務端運行框架的建立。而第一個階段創建的服務描述是為了第二個階段對服務端運行時框架建立服務的,所以我們有必要在對服務描述進行簡單的介紹。
目錄:
一、從服務描述(Service Description)談起
二、服務端架構體系概覽
三、終結點分發器選擇機制
一、從服務描述(Service Description)談起
當ServiceHost在被實例化的過程中,用于描述整個服務的ServiceDescription對象被創建出來。對于一個服務來說,它的核心包括:一組終結點列表和一組服務行為列表。這可以通過如下所示的ServiceDescription的定義看出來。
{
//其他成員
public KeyedByTypeCollection<IServiceBehavior> Behaviors { get; }
public ServiceEndpointCollection Endpoints { get; }
}
而對于終結點來說,對于它的ABC三要素,即地址(Address)、綁定(Binding)和契約(Contract)早已了然于胸了。所以用于描述終結點的ServiceEndpoint類型具有Address、Binding和Contract三個核心屬性。此外還有基于該終結點的行為列表,通過Behaviors屬性表示。ServiceEndpoint的定義如下所示。
{
//其他成員
public EndpointAddress Address { get; set; }
public Binding Binding { get; set; }
public ContractDescription Contract { get; }
public KeyedByTypeCollection<IEndpointBehavior> Behaviors { get; }
}
現在我們進一步分析用以描述服務契約的ContractDescription類型。由于服務契約本質上是一組相關操作的組合,所以ContractDescription的核心屬性是如下所示的表示所有操作描述的Operations屬性。除了操作描述列表之外,自然還有基于服務契約本身的行為列表。
{
//其他成員
public OperationDescriptionCollection Operations { get; }
public KeyedByTypeCollection<IContractBehavior> Behaviors { get; }
}
至于對服務操作的描述,對應的類型為OperationDescription。OperationDescription中定義了一系列基于服務操作的屬性,它們以及在之前的章節有過詳細的介紹了,在這里我們主要關注的是用以表示操作行為列表的屬性Behaviors。
{
//其他成員
public KeyedByTypeCollection<IOperationBehavior> Behaviors { get; }
}
上述從服務ServiceDescription到ServiceEndpoint,從ServiceEndpoint到ContractDescription,最終到OperationDescription的層次結構基本上可以通過下圖來表示。
在構建ServiceHost過程中創建的用于描述整個服務的ServiceDescription對象,最終成為了構建服務端運行時架構體系的基礎。而該架構體系在ServiceHost開啟的過程中被構建出來,這也是為什么在ServiceHost開啟之后對服務描述所作的任何該表都是無效的根本原因。
二、服務端架構體系概覽
為了讓讀者對服務端運行時架構體系的結構具有更加深刻的認識,我們針對一個具體的服務寄宿應用場景來進行介紹。假設我們采用如下的配置對服務CalculatorService進行寄宿。通過這段配置,三個基于WSHttpBinding的終結點被添加。
<system.serviceModel>
<services>
<service name="Artech.WcfServices.CalculatorService">
<endpoint address = "http://127.0.0.1:7777/CalculatorService"
binding = "wsHttpBinding"
contract = "Artech.WcfServices.ICalculator"
listenUri = "http://127.0.0.1:6666/CalculatorService"
listenUriMode= "Explicit"/>
<endpoint address = "http://127.0.0.1:8888/CalculatorService"
binding = "wsHttpBinding"
contract = "Artech.WcfServices.ICalculator"
listenUri = "http://127.0.0.1:6666/CalculatorService"
listenUriMode= "Explicit"/>
<endpoint address = "http://127.0.0.1:9999/CalculatorService"
binding = "wsHttpBinding"
contract = "Artech.WcfServices.ICalculator"
listenUri = "http://127.0.0.1:6666/CalculatorService"
listenUriMode= "Unique"/>
</service>
</services>
</system.serviceModel>
</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,則表明該終結點分發器與需要路有的消息匹配。
{
public abstract bool Match(Message message);
public abstract bool Match(MessageBuffer buffer);
}
終結點分發器在WCF的應用編程接口中通過類型System.ServiceModel.Dispatcher.EndpointDispatcher表示。EndpointDispatcher的部分定義如下面的代碼片斷所示,除了代表上述兩個消息篩選器的兩個屬性AddressFilter和ContractFilter之外,還有一個額外的整型的FilterPriority屬性。FilterPriority屬性表示篩選的優先級,當兩個以上終結點分發器同時與路由的消息匹配的情況下,由優先級最高的終結點分發器會被選用。代表FilterPriority的數據越大,意味著優先級越高。如果同時有兩個或者以上具有最高篩選優先級的終結終結點分發器,系統會拋出一個MultipleFilterMatchesException異常。
{
//其他成員
public MessageFilter AddressFilter { get; set; }
public MessageFilter ContractFilter { get; set; }
public int FilterPriority { get; set; }
}
為了滿足各種消息理由的需要,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這三種消息篩選器。
{
//其他成員
public AddressFilterMode AddressFilterMode { get; set; }
}
public enum AddressFilterMode
{
Exact,
Prefix,
Any
}
留言列表