在介紹終結點的ListenUriMode時,我們提到了兩個特殊的對象ChannelDispatcher和ChannelListener。這兩個對象在整個WCF的消息分發系統中具有重要的地位,在這節裡,我們對WCF的整個消息分發過程作一個簡單的介紹。
1、連接請求的監聽
當我們通過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(也就是終結點地址)。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="Artech.WcfServices.Services.CalculateService">
<endpoint address="http://127.0.0.1:9999/calculateservice" binding="basicHttpBinding"
contract="Artech.WcfServices.Contracts.ICalculate" listenUri="http://127.0.0.1:6666/calculateservice" />
<endpoint address="http://127.0.0.1:8888/calculateservice" binding="basicHttpBinding"
contract="Artech.WcfServices.Contracts.ICalculate" listenUri="http://127.0.0.1:6666/calculateservice" />
<endpoint address="http://127.0.0.1:7777/calculateservice" binding="basicHttpBinding"
contract="Artech.WcfServices.Contracts.ICalculate" />
</service>
</services>
</system.serviceModel>
</configuration>
當我們通過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的關系:
//---------------------------------------------------------------
// EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
//---------------------------------------------------------------
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(CalculateService)))
{
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();
}
}
}
}
最終輸出的結果印證了我們上面對ServiceHost、ChannelDispatcher、ChannelListener和EndpointDispatcher的關系:
ChannelDispatcher 1: ListenUri: http://127.0.0.1:6666/calculateservice
EndpointDispatcher 1: EndpointAddress: http://127.0.0.1:9999/calculateservice
EndpointDispatcher 2: EndpointAddress: http://127.0.0.1:8888/calculateservice
ChannelDispatcher 2: ListenUri: http://127.0.0.1:7777/calculateservice
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。
//---------------------------------------------------------------
// EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
//---------------------------------------------------------------
public class EndpointDispatcher
{
... ...
public MessageFilter AddressFilter { get; set; }
public MessageFilter ContractFilter { get; set; }
}
我們先來看看MessageFilter的定義, 如下所示,MessageFilter類定義兩個重載的Match方法,參數分別是Message和MessageBuffer。當MessageFilter的Match方法返回True,就表明該MessageFilter對象對應的EndpointDispatcher正是真正被請求的EndpointDispatcher。也就是說當ChannelDispatcher進行篩選的時候,會遍歷它所有的EndpointDispatcher,獲取他們的AddressFilter和ContractFilter,調用Match方法,如果兩者都返回true,則表明是真正的需要的EndpointDispatcher。
//---------------------------------------------------------------
// EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
//---------------------------------------------------------------
public abstract class MessageFilter
{
public abstract bool Match(Message message);
public abstract bool Match(MessageBuffer buffer);
}
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。
//---------------------------------------------------------------
// EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
//---------------------------------------------------------------
public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
{
... ...
public AddressFilterMode AddressFilterMode { get; set; }
}
public enum AddressFilterMode
{
Exact,
Prefix,
Any
}
其中Exact對應EndpointAddressMessageFilter;Prefix對應PrefixEndpointAddressMessageFilter;Any對應MatchAllMessageFilter。比如通過下面的代碼,將AddressFilter指定為MatchAllMessageFilter:
//---------------------------------------------------------------
// EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
//---------------------------------------------------------------
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
public class CalculateService:ICalculate
{
...
}