[WCF-Discovery] 實例演示:如何利用服務發現機製實現服務的“動態”調用?
前麵兩篇(《服務如何能被”發現”》和《客戶端如何能夠“探測”到可用的服務?》)我們分別介紹了可被發現服務如何被發布,以及客戶端如果探測可用的服務。接下來我們通過一個簡單的例子來演示如果創建和發布一個可被發現的服務,客戶端如何在不知道服務終結點地址的情況下動態探測可用的服務並調用之。該實例的解決方案采用如右圖所示的結構,即包含項目Service.Interface(類庫)、Client(控製台應用)和Service(控製台應用)分別定義服務契約、服務(包括服務寄宿)和客戶端程序。[源代碼從這裏下載,DynamicEndpoint方式進行服務調用源代碼從這裏下載]。
目錄
步驟一、創建服務契約和服務
步驟二、寄宿服務
步驟三、服務的“動態”調用
DynamicEndpoint
第一個步驟自然是在Service.Interface項目中定義代表服務契約的接口。我們還是采用屬性的計算服務的例子,為此我們定義了如下一個ICalculator接口。
1: using System.ServiceModel;
2: namespace Artech.ServiceDiscovery.Service.Interface
3: {
4: [ServiceContract(Namespace="https://www.artech.com/")]
5: public interface ICalculator
6: {
7: [OperationContract]
8: double Add(double x, double y);
9: }
10: }
接下來在Service這個控製台應用項目中定義實現上述契約接口的服務CalculatorService,該服務類型定義如下。
1: namespace Artech.ServiceDiscovery.Service
2: {
3: public class CalculatorService : ICalculator
4: {
5: public double Add(double x, double y)
6: {
7: return x + y;
8: }
9: }
10: }
接下來我們需要通過Service這個控製台應用作為宿主對上麵定義的CalculatorService服務進行寄宿,下麵是為此添加的配置。
1: <configuration>
2: <system.serviceModel>
3: <behaviors>
4: <serviceBehaviors>
5: <behavior>
6: <serviceDiscovery />
7: </behavior>
8: </serviceBehaviors>
9: <endpointBehaviors>
10: <behavior name="scopeMapping">
11: <endpointDiscovery enabled="true">
12: <scopes>
13: <add scope="https://www.artech.com/calculatorservice"/>
14: </scopes>
15: </endpointDiscovery>
16: </behavior>
17: </endpointBehaviors>
18: </behaviors>
19: <services>
20: <service name="Artech.ServiceDiscovery.Service.CalculatorService">
21: <endpoint address="https://127.0.0.1:3721/calculatorservice"
22: binding="ws2007HttpBinding"
23: contract="Artech.ServiceDiscovery.Service.Interface.ICalculator"
24: behaviorConfiguration="scopeMapping" />
25: <endpoint kind="udpDiscoveryEndpoint" />
26: </service>
27: </services>
28: </system.serviceModel>
29: </configuration>
在上麵這段配置中,被寄宿的終結點出了有一個基於WS2007HttpBinding的終結點外,還具有另一個UdpDiscoveryEndpoint標準終結點。此外,我還定義了一個名稱為scopeMapping的終結點行為,該行為通過EndpointDiscoveryBehavior行為定義了一個代表服務範圍的Uri:https://www.artech.com/calculatorservice。這個終結點行為最終被應用到了第一個終結點),就以為這該終結點將此Uri作為了它的服務範圍。最後,我還定義了一個默認的服務行為,而ServiceDiscoveryBehavior被定義其中。現在被寄宿的服務具有了ServiceDiscoveryBehavior行為和一個UdpDiscoveryEndpoint,所以它是一個可被發現的服務了。最後,該服務通過如下一段簡單的程序進行自我寄宿。
1: using System;
2: using System.ServiceModel;
3: namespace Artech.ServiceDiscovery.Service
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
10: {
11: host.Open();
12: Console.Read();
13: }
14: }
15: }
16: }
現在來編寫客戶端服務調用的程序。假設客戶端不知道服務的終結點地址,需要通過服務發現機製進行動態的探測。最終通過探測返回的終結點地址動態的創建服務代理對服務發起調用。我們不需要對客戶端程序添加任何配置,可用服務的探測和調用完全通過如下的代碼來實現。
1: using System;
2: using System.ServiceModel;
3: using System.ServiceModel.Discovery;
4: using Artech.ServiceDiscovery.Service.Interface;
5: namespace Artech.ServiceDiscovery.Client
6: {
7: class Program
8: {
9: static void Main(string[] args)
10: {
11: DiscoveryClient discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
12: FindCriteria criteria = new FindCriteria(typeof(ICalculator));
13: criteria.Scopes.Add(new Uri("https://www.artech.com/"));
14: FindResponse response = discoveryClient.Find(criteria);
15:
16: if (response.Endpoints.Count > 0)
17: {
18: EndpointAddress address = response.Endpoints[0].Address;
19: using(ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(new WS2007HttpBinding(),address))
20: {
21: ICalculator calculator = channelFactory.CreateChannel();
22: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));
23: }
24: }
25: Console.Read();
26: }
27: }
28: }
整段程序分為兩個部分,即可用服務的探測和對目標服務的調用。首先我基於創建的標準終結點UdpDiscoveryEndpoint創建DiscoveryClient對象。然後基於服務契約接口的類型(ICalculator)創建FindCriteria,並在它的Scopes集合中添加了一個Uri(https://www.artech.com/")。由於我們不曾指定FindCriteria的MatchBy屬性,默認采用基於前綴的服務範圍匹配方式,所以通過這個Uri和我們的目標服務是可以匹配的。將此FindCriteria對象作為輸入調用Find方法,並從返回的FindResponse中得到目標服務的終結點地址。最後用此終結點地址創建服務代理並進行服務調用。
整個實例程序編寫完畢,再啟動服務寄宿程序Service的前提下啟動客戶端程序Client,定義在Client中的服務調用能夠順利完成,並得到如下的輸出結果。
1: x + y = 3 when x = 1 and y = 2
在上麵的例子中我們演示客戶端在不知道目標服務地址的情況下如何服務發現機製進行服務的動態調用。從我們的演示來看,這需要兩個基本的步驟:首先需要借助於DiscoveryClient通過服務探測(或者解析)獲取進行服務調用必須的元數據(主要是目標服務終結點地址);然後根據獲取的元數據信息創建服務代理進行服務調用。那麼是否有一種方式能夠將這兩個步驟合二為一呢?答案是肯定的,這就涉及到對另一個標準終結點的使用,即DynamicEndpoint。
為了對DynamicEndpoint這個標準終結點的作用有一個感官的認識,我們借助於DynamicEndpoint對上麵例子中的服務調用方式進行相應的更改。我們先為控製台應用Client添加一個配置文件,並定義如下一段簡單的配置。
1: <configuration>
2: <system.serviceModel>
3: <client>
4: <endpoint name="calculatorservice"
5: kind="dynamicEndpoint"
6: binding="ws2007HttpBinding"
7: contract="Artech.ServiceDiscovery.Service.Interface.ICalculator"/>
8: </client>
9: </system.serviceModel>
10: </configuration>
在這段配置中,我定義了一個客戶端終結點。不過和我們之前的終結點配置有點不同,因為我們並沒有對地址進行相應的設置。之所以可以省略掉對目標服務終結點地址的設置,在於我們定義的是一個DynamicEndpoint(kind="dynamicEndpoint")。而我們進行服務調用的程序和基於普通終結點的調用方式完全一樣。運行修改後的程序,你會得到一樣的執行結果。
1: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))
2: {
3: ICalculator calculator = channelFactory.CreateChannel();
4: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));
5: }
DynamicEndpoint之所以能夠將服務探測和調用這兩個步驟統一起來,其本質在於DynamicEndpoint是由兩個終結點組合而成的。其中一個為用於進行服務探測的DiscoveryEndpoint;另一個用於真正服務調用的終結點,該終結點使用DynamicEndpoint的綁定和契約,而使用DiscoveryEndpoint探測的地址。關於DynamicEndpoint的組合性,也可以通過其定義看出來。
1: public class DynamicEndpoint : ServiceEndpoint
2: {
3: //其他成員
4: public DynamicEndpoint(ContractDescription contract, Binding binding);
5: public DiscoveryEndpointProvider DiscoveryEndpointProvider { get; set; }
6: public FindCriteria FindCriteria { get; set; }
7: }
從DynamicEndpoint的定義可以看出:我們隻需要通過指定終結點ABC三要素的綁定和契約就能夠構建DynamicEndpoint這個標準終結點,而地址這是通過DiscoveryEndpoint終結點動態探測獲得的。而具體負責創建這個DiscoveryEndpoint是通過屬性DiscoveryEndpointProvider屬性表示的DiscoveryEndpointProvider對象。至於FindCriteria屬性,自然就是在進行服務探測指定匹配條件。
DiscoveryEndpointProvider是一個抽象類,DiscoveryEndpoint終結點的創建通過定義在該類上的唯一的抽象方法GetDiscoveryEndpoint實現。而WCF為了定義了兩個具體的DiscoveryEndpointProvider,一個是UdpDiscoveryEndpointProvider,它會創建一個UdpDiscoveryEndpoint;另外一個為ConfigurationDiscoveryEndpointProvider,它會根據我們配置的來進行DiscoveryEndpoint的創建。下麵的代碼給出了DiscoveryEndpointProvider、UdpDiscoveryEndpointProvider和ConfigurationDiscoveryEndpointProvider的簡單定義,從中可以看出後兩個具體的DiscoveryEndpointProvider類型都是內部類型。
1: public abstract class DiscoveryEndpointProvider
2: {
3: public abstract DiscoveryEndpoint GetDiscoveryEndpoint();
4: }
5: internal class UdpDiscoveryEndpointProvider : DiscoveryEndpointProvider
6: {
7: //省略成員
8: }
9: internal class ConfigurationDiscoveryEndpointProvider : DiscoveryEndpointProvider
10: {
11: //省略成員
12: }
在默認的情況下DynamicEndpoint采用的DiscoveryEndpointProvider是UdpDiscoveryEndpointProvider,也就是一位著DiscoveryEndpoint在進行真正的服務調用之前會先創建一個UdpDiscoveryEndpoint來探測可用調用的服務的終結點地址。從這個意義上講,我們采用修改後采用DynamicEndpoint進行的服務調用,和之前先創建一個基於UdpDiscoveryEndpoint的DiscoveryClient對象探測出目標服務的終結點地址,在使用該地址創建服務代理進行服務調用的方式從本質上是一致的。
如果你不需要采用UdpDiscoveryEndpoint作為DynamicEndpoint默認使用的DiscoveryEndpoint,或者說你需要對被DynamicEndpoint使用的UdpDiscoveryEndpoint進行相應的設置,你都可以通過配置來完成。此外可供配置的還有表示服務探測匹配條件的FindCriteria。在下麵的培植中,我針對DynamicEndpoint采用的UdpDiscoveryEndpoint進行了相應的設置,並為FindCriteria添加了一個表示服務反問的Uri。
1: <configuration>
2: <system.serviceModel>
3: <client>
4: <endpoint name="calculatorservice"
5: kind="dynamicEndpoint"
6: endpointConfiguration="dynamicEndpointWithScope"
7: binding="ws2007HttpBinding"
8: contract="Artech.ServiceDiscovery.Service.Interface.ICalculator"/>
9: </client>
10: <standardEndpoints>
11: <dynamicEndpoint>
12: <standardEndpoint name="dynamicEndpointWithScope">
13: <discoveryClientSettings>
14: <endpoint kind="udpDiscoveryEndpoint" endpointConfiguration="adhocDiscoveryEndpointConfiguration"/>
15: <findCriteria>
16: <scopes>
17: <add scope="https://www.artech.com/"/>
18: </scopes>
19: </findCriteria>
20: </discoveryClientSettings>
21: </standardEndpoint>
22: </dynamicEndpoint>
23: <udpDiscoveryEndpoint>
24: <standardEndpoint name="adhocDiscoveryEndpointConfiguration" discoveryVersion="WSDiscovery11">
25: <transportSettings duplicateMessageHistoryLength="2048"
26: maxPendingMessageCount="5"
27: maxReceivedMessageSize="8192"
28: maxBufferPoolSize="262144"/>
29: </standardEndpoint>
30: </udpDiscoveryEndpoint>
31: </standardEndpoints>
32: </system.serviceModel>
33: </configuration>
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 14:34:51