[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="http://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="http://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="http://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:http://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("http://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(http://www.artech.com/")。由于我们不曾指定FindCriteria的MatchBy属性,默认采用基于前缀的服务范围匹配方式,所以通过这个Uri和我们的目标服务是可以匹配的。将此FindCriteria对象作为输入调用Find方法,并从返回的FindResponse中得到目标服务的终结点地址。最后用此终结点地址创建服务代理并进行服务调用。
整个实例程序编写完毕,再启动服务寄宿程序Service的前提下启动客户端程序Client,定义在Client中的服务调用能够顺利完成,并得到如下的输出结果。
1: x + y = 3 when x = 1 and y = 2
DynamicEndpoint
在上面的例子中我们演示客户端在不知道目标服务地址的情况下如何服务发现机制进行服务的动态调用。从我们的演示来看,这需要两个基本的步骤:首先需要借助于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="http://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>