[WCF-Discovery] 实例演示:如何利用服务发现机制实现服务的“动态”调用?
前面两篇(《服务如何能被”发现”》和《客户端如何能够“探测”到可用的服务?》)我们分别介绍了可被发现服务如何被发布,以及客户端如果探测可用的服务。接下来我们通过一个简单的例子来演示如果创建和发布一个可被发现的服务,客户端如何在不知道服务终结点地址的情况下动态探测可用的服务并调用之。该实例的解决方案采用如下图所示的结构,即包含项目Service.Interface(类库)、Client(控制台应用)和Service(控制台应用)分别定义服务契约、服务(包括服务寄宿)和客户端程序。[源代码从这里下载,DynamicEndpoint方式进行服务调用源代码从这里下载]。
目录
步骤一、创建服务契约和服务
步骤二、寄宿服务
步骤三、服务的“动态”调用
DynamicEndpoint
步骤一、创建服务契约和服务
第一个步骤自然是在Service.Interface项目中定义代表服务契约的接口。我们还是采用属性的计算服务的例子,为此我们定义了如下一个ICalculator接口。
using System.ServiceModel;
namespace Artech.ServiceDiscovery.Service.Interface
{
[ServiceContract(Namespace="http://www.artech.com/")]
public interface ICalculator
{
[OperationContract]
double Add(double x, double y);
}
}
namespace Artech.ServiceDiscovery.Service
{
public class CalculatorService : ICalculator
{
public double Add(double x, double y)
{
return x + y;
}
}
}
接下来我们需要通过Service这个控制台应用作为宿主对上面定义的CalculatorService服务进行寄宿,下面是为此添加的配置。
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceDiscovery />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="scopeMapping">
<endpointDiscovery enabled="true">
<scopes>
<add scope="http://www.artech.com/calculatorservice"/>
</scopes>
</endpointDiscovery>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service name="Artech.ServiceDiscovery.Service.CalculatorService">
<endpoint address="http://127.0.0.1:3721/calculatorservice"
binding="ws2007HttpBinding"
contract="Artech.ServiceDiscovery.Service.Interface.ICalculator"
behaviorConfiguration="scopeMapping"/>
<endpoint kind="udpDiscoveryEndpoint"/>
</service>
</services>
</system.serviceModel>
</configuration>
using System;
using System.ServiceModel;
namespace Artech.ServiceDiscovery.Service
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
{
host.Open();
Console.Read();
}
}
}
}
现在来编写客户端服务调用的程序。假设客户端不知道服务的终结点地址,需要通过服务发现机制进行动态的探测。最终通过探测返回的终结点地址动态的创建服务代理对服务发起调用。我们不需要对客户端程序添加任何配置,可用服务的探测和调用完全通过如下的代码来实现。
using System;
using System.ServiceModel;
using System.ServiceModel.Discovery;
using Artech.ServiceDiscovery.Service.Interface;
namespace Artech.ServiceDiscovery.Client
{
class Program
{
static void Main(string[] args)
{
DiscoveryClient discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
FindCriteria criteria = new FindCriteria(typeof(ICalculator));
criteria.Scopes.Add(new Uri("http://www.artech.com/"));
FindResponse response = discoveryClient.Find(criteria);
if (response.Endpoints.Count > 0)
{
EndpointAddress address = response.Endpoints[0].Address;
using(ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(new WS2007HttpBinding(),address))
{
ICalculator calculator = channelFactory.CreateChannel();
Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));
}
}
Console.Read();
}
}
}
整个实例程序编写完毕,再启动服务寄宿程序Service的前提下启动客户端程序Client,定义在Client中的服务调用能够顺利完成,并得到如下的输出结果。
x + y = 3 when x = 1 and y = 2
DynamicEndpoint
在上面的例子中我们演示客户端在不知道目标服务地址的情况下如何服务发现机制进行服务的动态调用。从我们的演示来看,这需要两个基本的步骤:首先需要借助于DiscoveryClient通过服务探测(或者解析)获取进行服务调用必须的元数据(主要是目标服务终结点地址);然后根据获取的元数据信息创建服务代理进行服务调用。那么是否有一种方式能够将这两个步骤合二为一呢?答案是肯定的,这就涉及到对另一个标准终结点的使用,即DynamicEndpoint。
为了对DynamicEndpoint这个标准终结点的作用有一个感官的认识,我们借助于DynamicEndpoint对上面例子中的服务调用方式进行相应的更改。我们先为控制台应用Client添加一个配置文件,并定义如下一段简单的配置。
<configuration>
<system.serviceModel>
<client>
<endpoint name="calculatorservice"
kind="dynamicEndpoint"
binding="ws2007HttpBinding"
contract="Artech.ServiceDiscovery.Service.Interface.ICalculator"/>
</client>
</system.serviceModel>
</configuration>
using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))
{
ICalculator calculator = channelFactory.CreateChannel();
Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));
}
public class DynamicEndpoint : ServiceEndpoint
{
//其他成员
public DynamicEndpoint(ContractDescription contract, Binding binding);
public DiscoveryEndpointProvider DiscoveryEndpointProvider { get; set; }
public FindCriteria FindCriteria { get; set; }
}
DiscoveryEndpointProvider是一个抽象类,DiscoveryEndpoint终结点的创建通过定义在该类上的唯一的抽象方法GetDiscoveryEndpoint实现。而WCF为了定义了两个具体的DiscoveryEndpointProvider,一个是UdpDiscoveryEndpointProvider,它会创建一个UdpDiscoveryEndpoint;另外一个为ConfigurationDiscoveryEndpointProvider,它会根据我们配置的来进行DiscoveryEndpoint的创建。下面的代码给出了DiscoveryEndpointProvider、UdpDiscoveryEndpointProvider和ConfigurationDiscoveryEndpointProvider的简单定义,从中可以看出后两个具体的DiscoveryEndpointProvider类型都是内部类型。
public abstract class DiscoveryEndpointProvider
{
public abstract DiscoveryEndpoint GetDiscoveryEndpoint();
}
internal class UdpDiscoveryEndpointProvider : DiscoveryEndpointProvider
{
//省略成员
}
internal class ConfigurationDiscoveryEndpointProvider : DiscoveryEndpointProvider
{
//省略成员
}
如果你不需要采用UdpDiscoveryEndpoint作为DynamicEndpoint默认使用的DiscoveryEndpoint, 或者说你需要对被DynamicEndpoint使用的UdpDiscoveryEndpoint进行相应的设置,你都可以通过配置来完成。此外可供配置的还有表示服务探测匹配条件的FindCriteria。在下面的培植中,我针对DynamicEndpoint采用的UdpDiscoveryEndpoint进行了相应的设置,并为FindCriteria添加了一个表示服务反问的Uri。
<configuration>
<system.serviceModel>
<client>
<endpoint name="calculatorservice"
kind="dynamicEndpoint"
endpointConfiguration="dynamicEndpointWithScope"
binding="ws2007HttpBinding"
contract="Artech.ServiceDiscovery.Service.Interface.ICalculator"/>
</client>
<standardEndpoints>
<dynamicEndpoint>
<standardEndpoint name="dynamicEndpointWithScope">
<discoveryClientSettings>
<endpoint kind="udpDiscoveryEndpoint" endpointConfiguration="adhocDiscoveryEndpointConfiguration"/>
<findCriteria>
<scopes>
<add scope="http://www.artech.com/"/>
</scopes>
</findCriteria>
</discoveryClientSettings>
</standardEndpoint>
</dynamicEndpoint>
<udpDiscoveryEndpoint>
<standardEndpoint name="adhocDiscoveryEndpointConfiguration" discoveryVersion="WSDiscovery11">
<transportSettings duplicateMessageHistoryLength="2048"
maxPendingMessageCount="5"
maxReceivedMessageSize="8192"
maxBufferPoolSize="262144"/>
</standardEndpoint>
</udpDiscoveryEndpoint>
</standardEndpoints>
</system.serviceModel>
</configuration>