閱讀377 返回首頁    go 阿裏雲 go 技術社區[雲棲]


[WCF-Discovery]服務如何能被”發現”

要讓作為服務消費者的客戶端能夠動態地發現可用的服務,首先的要求服務本身具有可被發現的特性。那麼到底一個可被發現的服務和一個一般的服務有何不同呢?或者說如何讓一個一般的服務在寄宿的時候能夠被它潛在的消費者“探測”到呢?

我們知道,WCF本質上就是消息交換的通信框架。不論是針對普通的服務操作的調用,還是定義在WS-Discovery中的服務的探測(Probe/PM)和解析(Resolve/RM),本質上都是一種。它們並沒有本質的不同,或者說唯一不同的就是消息的內容,前者是基於某個服務操作的請求和回複,而後者這是針對。

從消息交換的角度講,服務發現和元數據的獲取比較類似,因為它們都交換的消息都關於服務本身的一些信息。在《如何將一個服務發布成WSDL》中,我們介紹了元數據的發布具有兩種不同的方式:HTTP-GET和MEX終結點。服務發現機製對服務信息交換的實現與基於MEX終結點進行服務元數據交換的實現比較類似,因為它也需要一個特殊類型的終結點,即DiscoveryEndpoint。

在前麵介紹“標準終結點”的時候,我們列出的一係列標準終結點列表中就有一個DiscoveryEndpoint。一個服務必須具有一個DiscoveryEndpoint才能成為一個可被發現的服務,而客戶端也正是通過DiscoveryEndpoint來發現這相應的服務的。為了能夠更加深刻地認識這個標準終結點,我們不妨先來看看它的定義。

   1: public class DiscoveryEndpoint : ServiceEndpoint
   2: {
   3:     //其他成員
   4:     public DiscoveryEndpoint();
   5:     public DiscoveryEndpoint(Binding binding, EndpointAddress endpointAddress);
   6:     public DiscoveryEndpoint(DiscoveryVersion discoveryVersion, ServiceDiscoveryMode discoveryMode);
   7:     public DiscoveryEndpoint(DiscoveryVersion discoveryVersion, ServiceDiscoveryMode discoveryMode, Binding binding, EndpointAddress endpointAddress);
   8:  
   9:     public ServiceDiscoveryMode DiscoveryMode { get; }
  10:     public DiscoveryVersion DiscoveryVersion { get; }
  11:     public TimeSpan MaxResponseDelay { get; set; }
  12: }

對於一個終結點來說,當然也包括標準終結點,其核心永遠是、和三要素。從上麵的代碼片斷中我們不難發現,通過構造函數我們可以為DiscoveryEndpoint指定地址和綁定,卻無從指定其契約。無需顯式指定終結點契約是我們為何要定義這麼一個標準終結點的根本目的,而終結點最終采用的契約取決於兩個要素:其一、;其二、(Ad-Hoc或者Managed)。

WS-Discovery版本通過類型DiscoveryVersion表示,下麵的代碼片斷給出了DiscoveryVersion的定義。DiscoveryVersion具有三個靜態的隻讀屬性分別代表了三個主要的WS-Discovery版本。其中WSDiscoveryApril2005和WSDiscovery11代表兩個正式的版本1.0和1.1,而WSDiscoveryCD1則代表在2009年1月份針對WS-Discovery 1.1的第一個委員會草案(CD:Committee Draft)

   1: public sealed class DiscoveryVersion
   2: {   
   3:     //其他成員    
   4:     public string Name { get; }
   5:     public string Namespace {get; }
   6:     public Uri AdhocAddress { get; }
   7:     public MessageVersion MessageVersion { get; }
   8:  
   9:     public static DiscoveryVersion WSDiscovery11 { get; }
  10:     public static DiscoveryVersion WSDiscoveryApril2005 { get; }
  11:     public static DiscoveryVersion WSDiscoveryCD1 { get; }
  12: }

DiscoveryVersion具有四個隻讀屬性,Name表示相應版本的名稱。對於表示上述三個版本的DiscoveryVersion對象,該屬性的值分別為WSDiscoveryApril2005、WSDiscovery11和WSDiscoveryCD1(與上述的三個靜態隻讀屬性的名稱一致)。而Namespace和AdhocAddress則表示具體版本的WS-Discovery中采用的命名空間和在Ad-Hoc模式下各種廣播消息中使用的目標地址(即基於WS-Addressing的<To>報頭攜帶的地址)。MessageVersion表示選擇的消息版本(SOAP版本加上WS-Addressing的版本)。默認情況下,DiscoveryEndpoint的DiscoveryVersion屬性為WSDiscovery11。

而我們之前介紹的兩種典型的服務發現模式(《[WCF-Discovery] WCF-Discovery的協議基礎:WS-Discovery》),即Ad-Hoc和Managed則定義在枚舉ServiceDiscoveryMode中,該枚舉定義如下。在默認情況下,DiscoveryEndpoint的DiscoveryMode屬性值為Managed。

   1: public enum ServiceDiscoveryMode
   2: {
   3:     Adhoc,
   4:     Managed
   5: }

我們說DiscoveryEndpoint采用的契約由WS-Discovery的版本和服務發現模式決定,那麼對於這兩個要素的不同組合,最終被選用的終結點契約類型是什麼呢?為此,我編寫了如下一段測試程序:基於不同的DiscoveryVersion和ServiceDiscoveryMode創建DiscoveryEndpoint對象,最終打印出。

   1: DiscoveryEndpoint endpoint1;
   2: DiscoveryEndpoint endpoint2;
   3: Console.WriteLine("{0,-25}{1,-35}{2, -30}", "", "Ad-Hoc", "Managed");
   4:  
   5: endpoint1 = new DiscoveryEndpoint(DiscoveryVersion.WSDiscoveryApril2005, ServiceDiscoveryMode.Adhoc);
   6: endpoint2 = new DiscoveryEndpoint(DiscoveryVersion.WSDiscoveryApril2005, ServiceDiscoveryMode.Managed);
   7: Console.WriteLine("{0,-25}{1,-35}{2, -30}", "WSDiscoveryApril2005", endpoint1.Contract.ContractType.Name, endpoint2.Contract.ContractType.Name);
   8:  
   9: endpoint1 = new DiscoveryEndpoint(DiscoveryVersion.WSDiscovery11, ServiceDiscoveryMode.Adhoc);
  10: endpoint2 = new DiscoveryEndpoint(DiscoveryVersion.WSDiscovery11, ServiceDiscoveryMode.Managed);
  11: Console.WriteLine("{0,-25}{1,-35}{2, -30}", "WSDiscovery11", endpoint1.Contract.ContractType.Name, endpoint2.Contract.ContractType.Name);
  12:  
  13: endpoint1 = new DiscoveryEndpoint(DiscoveryVersion.WSDiscoveryCD1, ServiceDiscoveryMode.Adhoc);
  14: endpoint2 = new DiscoveryEndpoint(DiscoveryVersion.WSDiscoveryCD1, ServiceDiscoveryMode.Managed);
  15: Console.WriteLine("{0,-25}{1,-35}{2, -30}", "WSDiscoveryCD1", endpoint1.Contract.ContractType.Name, endpoint2.Contract.ContractType.Name);

我們將輸出的結果通過如下麵的表格來表示。從中我們不難發現,針對不同的WS-Discovery版本和服務發現模式組合,最終選擇的服務契約類型都是不同的。服務契約類型的名稱的命名規則為}。

  Ad-Hoc Managed
WSDiscoveryApril2005 IDiscoveryContractAdhocApril2005 IDiscoveryContractManagedApril2005
WSDiscovery11 IDiscoveryContractAdhoc11 IDiscoveryContractManaged11
WSDiscoveryCD1 IDiscoveryContractAdhocCD1 IDiscoveryContractManagedCD1

上述的6個契約類型對應著6個接口。不過,這些都是,並不對外公布,不過我們可以通過Reflector察看它們的定義。現在我們就來簡單看看針對WS-Discovery 1.1下分別針對Ad-Hoc和Managed模式的服務契約接口IDiscoveryContractAdhoc11和IDiscoveryContractManaged11的定義。

IDiscoveryContractAdhoc11:

   1: [ServiceContract(Name = "TargetService", Namespace = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01", CallbackContract = typeof(IDiscoveryResponseContract11))]
   2: internal interface IDiscoveryContractAdhoc11
   3: {    
   4:     [OperationContract(Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Probe", IsOneWay = true, AsyncPattern = true)]
   5:     IAsyncResult BeginProbeOperation(ProbeMessage11 request, AsyncCallback callback, object state);
   6:     [OperationContract(Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Resolve", IsOneWay = true, AsyncPattern = true)]
   7:     IAsyncResult BeginResolveOperation(ResolveMessage11 request, AsyncCallback callback, object state);
   8:     void EndProbeOperation(IAsyncResult result);
   9:     void EndResolveOperation(IAsyncResult result);
  10:     [OperationContract(Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Probe", IsOneWay = true)]
  11:     void ProbeOperation(ProbeMessage11 request);
  12:     [OperationContract(Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Resolve", IsOneWay = true)]
  13:     void ResolveOperation(ResolveMessage11 request);
  14: }

IDiscoveryContractManaged11:

   1: [ServiceContract(Name = "DiscoveryProxy", Namespace = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01")]
   2: internal interface IDiscoveryContractManaged11
   3: {
   4:     [OperationContract(Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Probe", ReplyAction = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/ProbeMatches", AsyncPattern = true)]
   5:     IAsyncResult BeginProbeOperation(ProbeMessage11 request, AsyncCallback callback, object state);
   6:     [OperationContract(Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Resolve", ReplyAction = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/ResolveMatches", AsyncPattern = true)]
   7:     IAsyncResult BeginResolveOperation(ResolveMessage11 request, AsyncCallback callback, object state);
   8:     ProbeMatchesMessage11 EndProbeOperation(IAsyncResult result);
   9:     ResolveMatchesMessage11 EndResolveOperation(IAsyncResult result);
  10:     [OperationContract(Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Probe", ReplyAction = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/ProbeMatches")]
  11:     ProbeMatchesMessage11 ProbeOperation(ProbeMessage11 request);
  12:     [OperationContract(Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Resolve", ReplyAction = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/ResolveMatches")]
  13:     ResolveMatchesMessage11 ResolveOperation(ResolveMessage11 request);
  14: }

我們說服務契約本質上定義了采用的消息交換模式和被交換的消息的格式。對於客戶端驅動的惡服務發現來說,采用的服務交換不外乎兩種類型:和這在前麵針對WS-Discovery的部分有過詳細的介紹。所以,服務契約IDiscoveryContractAdhoc11和IDiscoveryContractManaged11實際定義了兩組代表著這兩種消息交換類型的操作ProbeOperation和ResolveOperation,一組是同步操作,另一組是異步操作。至於契約的名稱、命名空間,以及操作的Action,ReplyAction在通過相應的ServiceContractAttribute和OperationContractAttribute特性進行相應的定義,以確保和WS-Discovery 1.1規範保持一致。

除了DiscoveryMode和DiscoveryVersion這兩個隻讀屬性,DiscoveryEndpoint還具有一個可讀可寫的屬性MaxResponseDelay,表示服務相應Probe請求的PM消息延遲發送允許的時間範圍。在此MaxResponseDelay屬性規定的時間範圍內,服務的用於響應單個Probe請求的所有PM都將發送出去。如果同時發送所有的PM,則可能發生(Network Storming)。為了防止發生這種情況,響應服務在每個PM發送之間具有一個隨機延遲。隨機延遲的範圍是從0到MaxResponseDelay。如果MaxResponseDelay設置為 0(默認值),則在不使用任何延遲的緊湊循環中發送PM消息。否則,在發送PM消息時將應用隨機延遲,以便發送所有PM消息所用的總時間不會超過MaxResponseDelay。

如果你采用編程的方式使用DiscoveryEndpoint,你可以通過在構造函數中傳入相應的參數決定采用的WS-Discovery版本和服務發現模式,並通過屬性賦值的方式決定MaxResponseDelay的值。如果采用配置的方式,這個標準終結點對應的配置元素也同樣提供相應的配置屬性。

   1: <standardEndpoints>
   2:    <udpDiscoveryEndpoint>
   3:       <standardEndpoint name="adhocDiscoveryEndpointConfiguration" discoveryVersion="WSDiscovery11" maxResponseDelay="00:00:00.600" />  
   4:    </udpDiscoveryEndpoint>
   5: </standardEndpoints>

由於DiscoveryEndpoint需要顯式地指定其地址,所以它隻能以的方式進行消息交換。由於WS-Discovery中的Ad-Hoc模式采用形式的消息交換,為此WCF為我們創建另一個標準的終結點UdpDiscoveryEndpoint。如下麵的代碼片斷所示,UdpDiscoveryEndpoint具有兩個基本的屬性MulticastAddress和TransportSettings。前者代表采用的廣播地址,默認值為“”,該值也是代表默認IPV4廣播地址的靜態隻讀屬性DefaultIPv4MulticastAddress的值。而另一個代表IPV6默認廣播地址的隻讀屬性DefaultIPv4MulticastAddress的值為“”。後者代表針對UDP傳輸層的相關設置。

   1: public class UdpDiscoveryEndpoint : DiscoveryEndpoint
   2: {
   3:     //其他成員
   4:     public static readonly Uri DefaultIPv4MulticastAddress;
   5:     public static readonly Uri DefaultIPv6MulticastAddress;
   6:  
   7:     public Uri MulticastAddress { get; set; }
   8:     public UdpTransportSettings TransportSettings { get; }
   9: }

而針對UDP傳輸層的設置又具有若幹選項,這可以從UdpTransportSettings的屬性成員的定義就可以看出來。

   1: public class UdpTransportSettings
   2: {
   3:     public int DuplicateMessageHistoryLength { get; set; }
   4:     public long MaxBufferPoolSize { get; set; }
   5:     public int MaxMulticastRetransmitCount { get; set; }
   6:     public int MaxPendingMessageCount { get; set; }
   7:     public long MaxReceivedMessageSize { get; set; }
   8:     public int MaxUnicastRetransmitCount { get; set; }
   9:     public string MulticastInterfaceId { get; set; }
  10:     public int SocketReceiveBufferSize { get; set; }
  11:     public int TimeToLive { get; set; }
  12: }

下麵的列表列出了針對UdpTransportSettings每個屬性所代表的含義:

  • DuplicateMessageHistoryLength:傳輸用於標識重複消息的最大消息哈希數,默認值為 4112;
  • MaxBufferPoolSize:傳輸使用的任何緩衝池的最大大小,默認值為524288;
  • MaxMulticastRetransmitCount:應重新傳輸多播消息的最大次數(第一次發送除外),默認值為2;
  • MaxPendingMessageCount:已經接收但尚未從每個通道實例的輸入隊列中移除的消息的最大數量,默認值為32;
  • MaxReceivedMessageSize:綁定可處理的消息的最大大小,默認值為65507;
  • MaxUnicastRetransmitCount:應重新傳輸單播消息的最大次數(第一次發送除外),默認值為1;
  • MulticastInterfaceId:該值唯一地標識在發送和接收多播消息時所使用的網絡適配器,默認值為null;
  • SocketReceiveBufferSize:基礎 WinSock 套接字上的接收緩衝區的大小,默認值為55536;
  • TimeToLive:多播數據包可以遍曆的網絡段躍點數,默認值為1。

標準終結點UdpDiscoveryEndpoint對應的配置元素同樣定義了相應的配置屬性是你能過對它采用的廣播地址以及UDP傳輸層進行自由的配置。下麵給出了一個配置實例。

   1: <udpDiscoveryEndpoint>
   2:   <standardEndpoint name="adhocDiscoveryEndpointConfiguration" discoveryVersion="WSDiscovery11">
   3:     <transportSettings duplicateMessageHistoryLength="2048"
   4:                        maxPendingMessageCount="5"
   5:                        maxReceivedMessageSize="8192"
   6:                        maxBufferPoolSize="262144"/>
   7:   </standardEndpoint>
   8: </udpDiscoveryEndpoint>

我們之前就已經說,客戶端用於獲取可用服務發起的請求,和基於普通服務調用的消息請求並沒有本質的不同。匹配的服務在接收到客戶端發送的Probe/Resolve請求後,會將自己的信息包含在PM/RM消息中進行回複。現在我們討論是一個核心的問題:?

對於普通的服務調用,回複消息的內容最初來源於針對服務實例的操作方法的調用的結果。針對服務發現的Probe/Resolve請求也是一樣,服務端依然存在一個用於返回目標服務信息的“”,並且這個服務的實現了添加到目標服務的DiscoveryEndpoint的契約接口。這個服務的類型就是抽象類DiscoveryService的子類。DiscoveryService的定於如下,可見它實現了DiscoveryEndpoint基於不同WS-Discovery版本在Ad-Hoc和Managed模式下的6個契約接口。

   1: public abstract class DiscoveryService : 
   2:     IDiscoveryContractAdhocApril2005, 
   3:     IDiscoveryContractManagedApril2005, 
   4:     IDiscoveryContractAdhoc11, 
   5:     IDiscoveryContractManaged11, 
   6:     IDiscoveryContractAdhocCD1, 
   7:     IDiscoveryContractManagedCD1, ...
   8: {
   9:     //省略成員
  10: }

clip_image002

知道真正用於實現服務發現的服務,我們需要考慮另一個問題:這個繼承自DiscoveryService的發現服務在接收到服務發現請求後被激活的。當用於寄宿服務的ServiceHost對象被開啟之後,服務的每個終結點都會轉換成一個EndpointDispatcher對象,這當然也包括上述的DiscoveryEndpoint。激活的服務實例被封裝在一個InstanceContext對象中,而服務對象和用於封裝服務對象的InstanceContext分別通過針對EndpointDispatcher對DispatchRuntime的兩個特殊的組件來提供,即InstanceProvider和InstanceContextProvider。。右圖大體上揭示了整個發現服務的激活機製。

在WCF的具體實現中,這個自定義的InstanceProvider和InstanceContextProvider是一個內部的類型ServiceDiscoveryInstanceContextProvider(它同時實現了IInstanceProvider和IInstanceContextProvider兩個接口)。而最終將它應用到DiscoveryEndpoint對應的EndpointDispatcher的則是通過一個服務行為來實現的,這個服務行為的類型是System.ServiceModel.Discovery.ServiceDiscoveryBehavior。所以,一個服務能夠成為一個可被發現的服務。


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 14:34:56

  上一篇:go  [WCF-Discovery] WCF-Discovery的協議基礎:WS-Discovery
  下一篇:go  [WCF-Discovery] 客戶端如何能夠“探測”到可用的服務?