[WCF-Discovery] 客戶端如何能夠“探測”到可用的服務?
當應用了ServiceDiscoveryBehavior行為的服務通過標準終結點DiscoveryEndpoint被發布出來之後(《[WCF-Discovery]服務如何能被”發現”》),客戶端就可以按照WS-Discovery中定義的方式對可用的目標方式進行探測和解析了。由於這個過程本質上就是一次普通的服務調用,具體來說是針對發布發現服務(非目標服務)的標準終結點DiscoveryEndpoint的調用,所以客戶端也需要具有這麼一個匹配的終結點。
目錄:
一、DiscoveryClient
二、FindCriteria/FindResponse
三、ResolveCriteria/ResolveResponse
客戶端針對可用目標服務的探測與解析都是通過DiscoveryClient對象來實現的,下麵的代碼片斷給出了DiscoveryClient的定義。我們可以直接通過一個DiscoveryEndpoint對象,或者是DiscoveryEndpoint的配置名稱來創建DiscoveryClient對象。
1: public sealed class DiscoveryClient : ICommunicationObject, IDisposable,...
2: {
3: //事件
4: public event EventHandler<FindCompletedEventArgs> FindCompleted;
5: public event EventHandler<ResolveCompletedEventArgs> ResolveCompleted;
6:
7: //構造函數
8: public DiscoveryClient(DiscoveryEndpoint discoveryEndpoint);
9: public DiscoveryClient(string endpointConfigurationName);
10:
11: //Find
12: public FindResponse Find(FindCriteria criteria);
13: public void FindAsync(FindCriteria criteria);
14: public void FindAsync(FindCriteria criteria, object userState);
15:
16: //Resolve
17: public ResolveResponse Resolve(ResolveCriteria criteria);
18: public void ResolveAsync(ResolveCriteria criteria);
19: public void ResolveAsync(ResolveCriteria criteria, object userState);
20: }
DiscoveryClient定義了兩套方法,一套是,另一套則是。實際上Find相當於是WS-Discovery中的,而Resolve自然就是WS-Discovery中的了。其中Find/Resolve采用同步的調用方式,而FindAsync/ ResolveAysnc則采用異步調用,異步調用完成的時候會觸發事件。
不論是用於可用服務探測的Find/FindAsync,還是用於目標服務解析的Resolve/ResolveAysnc,都需要指定相應的匹配條件。前者對應的匹配條件通過類型FindCriteria來表示,而後者匹配條件的類型則是ResolveCriteria。
同步方法Find/Resolve的返回類型分別為FindResponse和ResolveResponse。而對於異步調用,則可以通過注冊的FindCompleted/ResolveCompleted事件參數中獲取類型為FindResponse/ResolveResponse的返回值。這兩個事件的參數類型分別為FindCompletedEventArgs和ResolveCompletedEventArgs,這個兩個類型和它們的基類System.ComponentModel.AsyncCompletedEventArgs定義如下。
1: public class FindCompletedEventArgs : AsyncCompletedEventArgs
2: {
3: //其他成員
4: public FindResponse Result { get; }
5: }
6: public class ResolveCompletedEventArgs : AsyncCompletedEventArgs
7: {
8: //其他成員
9: public ResolveResponse Result { get; }
10: }
11: public class AsyncCompletedEventArgs : EventArgs
12: {
13: //其他成員
14: public bool Cancelled { get; }
15: public Exception Error { get; }
16: public object UserState { get; }
17: }
代表Probe請求的Find方法接受一個FindCriteria類的輸入參數作為進行探測可用目標的匹配條件,該類型的主要的屬性成員定義如下。其中ContractTypeNames代表探測的目標服務實現的契約類型列表,而Scopes和ScopeMatchBy則分別代表了用於探測目標的的範圍和對範圍進行匹配的方式。
1: public class FindCriteria
2: {
3: //其他成員
4: public static readonly Uri ScopeMatchByExact;
5: public static readonly Uri ScopeMatchByLdap;
6: public static readonly Uri ScopeMatchByNone;
7: public static readonly Uri ScopeMatchByPrefix;
8: public static readonly Uri ScopeMatchByUuid;
9:
10: public Collection<XmlQualifiedName> ContractTypeNames { get; }
11: public Collection<Uri> Scopes { get; }
12: public Uri ScopeMatchBy { get; set; }
13: }
目標的探測範圍通過一個Uri的集合表示。客戶端要通過範圍進行目標服務的探測,前提是目標服務預先得與表示範圍的Uri相關聯。服務(實際上是指服務的某個終結點)的範圍關聯通過終結點行為EndpointDiscoveryBehavior來指定。如下麵的代碼片斷所示,和FindCriteria一樣,EndpointDiscoveryBehavior同樣具有一個Uri集合類型的Scopes屬性。
1: public class EndpointDiscoveryBehavior : IEndpointBehavior
2: {
3: //其他成員
4: public Collection<Uri> Scopes { get; }
5: }
在服務寄宿的時候,我們將表示服務範圍的Uri列表定義在EndpointDiscoveryBehavior終結點行為中,並通過將此行為應用在寄宿服務相應的終結點上,從而實現了服務(終結點)與範圍的關聯。在下麵的配置中,我定義了一個名為scopeMatch的終結點行為將表示服務範圍的兩個Uri應用到了服務的終結點上。
1: <configuration>
2: <system.serviceModel>
3: <services>
4: <service ...>
5: <endpoint behaviorConfiguration="scopeMatch" .../>
6: ...
7: </service>
8: </services>
9: <behaviors>
10: <endpointBehaviors>
11: <behavior name="scopeMatch">
12: <endpointDiscovery>
13: <scopes>
14: <add scope="https://www.example.com/calculator"/>
15: <add scope="ldap:///ou=engineering,o=examplecom,c=us"/>
16: </scopes>
17: </endpointDiscovery>
18: </behavior>
19: </endpointBehaviors>
20: </system.serviceModel>
21: </configuration>
當服務接收到帶有服務範圍列表作為匹配條件的探測請求時,在進行匹配判斷的時候就驗證相應終結點關聯的範圍是否指定的範圍之類。至於具體采用的範圍匹配的邏輯,則取決於FindCriteria的ScopeMatchBy屬性。
FindCriteria的ScopeMatchBy屬性類型依然是Uri。WCF預選定義了5個Uri代表相應的進行範圍匹配的5種算法,它們對應著定義在FindCriteria的5個靜態隻讀屬性:ScopeMatchByExact、ScopeMatchByLdap、ScopeMatchByNone、ScopeMatchByPrefix和ScopeMatchByUuid。
1: public class FindCriteria
2: {
3: //其他成員
4: public static readonly Uri ScopeMatchByExact;
5: public static readonly Uri ScopeMatchByLdap;
6: public static readonly Uri ScopeMatchByNone;
7: public static readonly Uri ScopeMatchByPrefix;
8: public static readonly Uri ScopeMatchByUuid;
9:
10: public Uri ScopeMatchBy { get; set; }
11: }
下麵的列表列出了這5個靜態字段分別代表了何種服務範圍匹配算法,以及各自具有怎樣的Uri值。實際上這些代表服務範圍匹配算法的Uri也是定義在WS-Discovery規範之中,但是為了避免為實現對不同版本的WS-Discovery的支持而采用不同的Uri,WCF在這裏並沒有真正地采用定義在相應版本的WS-Discovery中的Uri,而是定義了自己的常量。在對Probe消息進行序列化的時候,會轉換成相應WS-Discovery支持的Uri。
- ScopeMatchByExact:對Uri進行精確匹配,Uri為https://schemas.microsoft.com/ws/2008/06/discovery/strcmp0;
- ScopeMatchByPrefix:將指定的Uri作為服務範圍的前綴進行匹配,Uri為https://schemas.microsoft.com/ws/2008/06/discovery/rfc;
- ScopeMatchByLdap:按使用LDAP URL的段來匹配範圍,Uri為https://schemas.microsoft.com/ws/2008/06/discovery/ldap;
- ScopeMatchByUuid:通過使用UUID字符串來完全匹配範圍,Uri為https://schemas.microsoft.com/ws/2008/06/discovery/uuid;
- ScopeMatchByNone:僅匹配那些未指定範圍的服務,Uri為https://schemas.microsoft.com/ws/2008/06/discovery/none。
如果采用ScopeMatchByExact,進行精確匹配是區分大小寫的。而基於前綴匹配的ScopeMatchByPrefix,實際上按進行匹配。搜索 https://contoso/building1 與範圍為 https://contoso/building/floor1 的服務相匹配。請注意,該搜索與https://contoso/building100 不匹配,因為最後兩個段不匹配。ScopeMatchBy的值必須指定為上述的5種Uri之一,其他各式的Uri是無效的。如果未指定範圍匹配規則,則使用ScopeMatchByPrefix。
按照WS-Discovery定義的消息交換模式來看,客戶端針對Find/FindAsync方法調用實際上就是發送Probe請求。符合匹配條件的目標服務會回複以PM消息,該消息中會包含服務相關的元數據信息。最終這些PM消息中的內容會被提取出來,被封裝成FindResponse對象並最為Find方法的返回值(或者事件參數FindCompletedEventArgs的Result屬性)。接下來,我們將重點介紹FindResponse這個類型。
1: public class FindResponse
2: {
3: //其他成員
4: public DiscoveryMessageSequence GetMessageSequence(EndpointDiscoveryMetadata endpointDiscoveryMetadata);
5: public Collection<EndpointDiscoveryMetadata> Endpoints { get; }
6: }
如上麵的代碼所示,FindResponse具有一個核心的隻讀屬性Endpoints,其類型是一個元素類型為EndpointDiscoveryMetadata的集合。顧名思義,EndpointDiscoveryMetadata就是代表探測到的服務製定匹配條件的服務的終結點的元數據。EndpointDiscoveryMetadata定義如下,通過相應的屬性可以得到代表目標服務終結點的地址、契約類型列表、監聽地址、服務範圍和擴展和版本相關信息。
1: public class EndpointDiscoveryMetadata
2: {
3: //其他成員
4: public EndpointAddress Address { get; set; }
5: public Collection<XmlQualifiedName> ContractTypeNames { get; }
6: public Collection<XElement> Extensions { get; }
7: public Collection<Uri> ListenUris { get; }
8: public Collection<Uri> Scopes { get; }
9: public int Version { get; set; }
10: }
FindResponse除了具有一個表述目標服務終結點元數據的Endpoints屬性之外,還具有一個GetMessageSequence方法,該方法以EndpointDiscoveryMetadata對象作為輸入,返回一個System.ServiceModel.Discovery.DiscoveryMessageSequence對象。DiscoveryMessageSequence被稱為消息序列,涉及到定義在WS-Discovery中的一個重要的概念應用序列(Application Sequence/AppSequence)。簡單起見,我們可以這樣來理解:在采用廣播模式的服務發現截至無法確保消息的有序接收,即不能確保消息按照它被發送的順序被接收(先發先至),所以需要相應的序號封裝在一個被稱為AppSequence的報頭中被發送。DiscoveryMessageSequence類型定義如下,它的三個隻讀屬性分別對應著AppSequence報頭的相應的屬性(Attribute),具體的含義請參考WS-Discovery規範。
1: public class DiscoveryMessageSequence : ...
2: {
3: //其他成員
4: public long InstanceId {get; }
5: public long MessageNumber {get; }
6: public Uri SequenceId {get; }
7: }
上麵我們介紹了用於進行可用服務探測的Find/FindAsync操作的輸入和輸出,接下倆我們按照相同的方式來分析用於進行服務解析的Resolve/ResolveAsync操作的輸入和輸出。首先來介紹一下用於封裝匹配條件的ResolveCriteria類型,下麵給出了它核心的屬性定義。
1: public class ResolveCriteria
2: {
3: //其他成員
4: public EndpointAddress Address { get; set; }
5: public Collection<XElement> Extensions { get; }
6: public TimeSpan Duration { get; set; }
7: }
其中Address屬性表示被解析的服務的終結點地址,而Exntesions代表以XElement集合表示的擴展信息。Duration屬性表示Resolve操作執行的超時時限,即要求對於Resolve請求,在規定的時限內必須得到回複。如果沒有進行顯式設置,Durarion屬性采用默認值20秒。
而作為Resolve/ResolveAsync輸出的ResolveResponse類型定義很簡單。它具有兩個核心的隻讀屬性,代表被解析後的服務終結點元數據的EndpointDiscoveryMetadata屬性和代表消息序列的MessageSequence屬性。
1: public class ResolveResponse
2: {
3: //其他成員
4: public EndpointDiscoveryMetadata EndpointDiscoveryMetadata { get;}
5: public DiscoveryMessageSequence MessageSequence { get;}
6: }
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 14:34:52