程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF後續之旅(16) 消息是如何分發到Endpoint的--消息篩選(Message Filter)

WCF後續之旅(16) 消息是如何分發到Endpoint的--消息篩選(Message Filter)

編輯:關於.NET

在介紹終結點的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
{
  ...
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved