wcf客户端动态嗅探服务实例

   参照博客园蒋金楠老师的博客和MSDN的一片文章,现在把wcf客户端动态嗅探wcf服务用一个实例来做出总结。

    在以往的wcf客户端应用的时候,我们需要提供客户服务地址和端口或者管道,当然有时候需要我们服务端公开元数据,从而实现客户端的搭建使用,这样的使用有两个局限性:

1、分配给客户端的端口或者管道必须可用,也就是说应用程序开发人员或管理员必须想这或者提供某种方法

2、客户端必须提供提前知道的服务端点地址,包括端口号和服务器或管道名称

   为了避免这俩个约束,理想的情况下,服务能够使用任何可用地址,而客户端就运行时动态的发现此地址。事实上,存在一种基于行业标准的解决方案可行,用于规定此发现的定位方式。也就是我们现在要实现的客户端动态嗅探服务技术,简单的讲就是跟无线路由发布信号,无线网卡捕获信号工作流程一样。

一、地址的发现

     发现依赖于用户数据报协议(UDP),与传输控制协议(TCP)不同,UDP是无连接协议,在数据包发送者和接受者之间不需要建立直接连接。客户端使用UDP传播对任何支持约定类型的端点的发送请求,服务所支持的专门发现端点接受的这些请求。发现端点的实现将相应客户端,以提供支持指定约定的服务端点的地址。客户端发送服务后对其进行调用,到这一步就和普通的wcf调用一样了。

与元数据交换 (MEX) 端点非常类似,WCF 提供了类型为 UdpDiscoveryEndpoint 的标准发现端点:

public class DiscoveryEndpoint : ServiceEndpoint

{...}

public class UdpDiscoveryEndpoint : DiscoveryEndpoint

{...}

通过在服务支持的行为集合中添加 ServiceDiscoveryBehavior,可以使主机实现该端点。可以通过以下编程方式实现此目的:

ServiceHost host = new ServiceHost(...); 
host.AddServiceEndpoint(new UdpDiscoveryEndpoint());
ServiceDiscoveryBehavior discovery = new ServiceDiscoveryBehavior();
host.Description.Behaviors.Add(discovery);
host.Open();

当然我们也可以通过配置文件来添加发现端点

<services>
<service name = "MyService">
<endpoint
kind = "udpDiscoveryEndpoint"
/>
...
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceDiscovery/>
</behavior>
</serviceBehaviors>
</behaviors>

二、动态地址

    发现方式和服务主机定义其端点的方式不同,可以采用动态变化的,但是,当客户端使用的使用需要发现并查找出里面一个可用的地址,在这种情况下,服务可以基于任何可用的端口或者管道对其发现服务的自动动态的配置。

为了动态地址自动化,我们写了DiscoveryHelper静态帮助类,其中包含两个属性:AvailableIpcBaseAddress 和 AvailableTcpBaseAddress

public static class DiscoveryHelper
{
public static Uri AvailableIpcBaseAddress
{get;}
public static Uri AvailableTcpBaseAddress
{get;}
}

实现 AvailableIpcBaseAddress 很简单,因为任何唯一命名的管道都可以实现,该属性使用新的全局唯一标识符 (GUID) 命名管道。对于实现 AvailableTcpBaseAddress,需要通过打开端口 0 查找可用 TCP 端口。

Uri baseAddress = DiscoveryHelper.AvailableTcpBaseAddress;
ServiceHost host = new ServiceHost(typeof(MyService),baseAddress);
host.AddDefaultEndpoints();
host.Open();
<service name = "MyService">
<endpoint
kind = "udpDiscoveryEndpoint"
/>
</service>
<serviceBehaviors>
<behavior>
<serviceDiscovery/>
</behavior>
</serviceBehaviors>

从上面的方式可以看出,只是想获得服务的动态基地址,需要配置文件中或以编程方式添加发现。我们可以使用EnableDiscovery主机扩展简化这些步骤:

public static class DiscoveryHelper
{
public static void EnableDiscovery(this ServiceHost host,bool enableMEX = true);
}

在使用 EnableDiscovery 时,不需要编程步骤或配置文件:

Uri baseAddress = DiscoveryHelper.AvailableTcpBaseAddress;
ServiceHost host = new ServiceHost(typeof(MyService),baseAddress);
host.EnableDiscovery();
host.Open();

如果主机尚未为服务定义端点,EnableDiscovery 将添加默认端点。此外,EnableDiscovery 默认向服务的基址上添加 MEX 端点。

客户端步骤

客户端使用 DiscoveryClient 类发现支持指定约定的所有服务的全部端点地址,这里我们使用到了DiscoveryClient,和所有的代理类似,客户端必须向代理的构造函数提供有关目标端点的信息,为此,客户端可以使用配置文件指定端点或以编程的方式提供标准的UDP发现点。因为不需要地址或者绑定等更多信息,然后,客户端调用Find方法,并通过FindCriteria实例为其提供发现的约定类型:

public sealed class DiscoveryClient : ICommunicationObject
{
public DiscoveryClient();
public DiscoveryClient(string endpointName);
public DiscoveryClient(DiscoveryEndpoint discoveryEndpoint);
public FindResponse Find(FindCriteria criteria);
//More members
}
public class FindCriteria

{

public FindCriteria(Type contractType);
//More members

}

Find 返回一个 FindResponse 实例,其中包含所有已发现端点的集合:

public class FindResponse

{
public Collection<EndpointDiscoveryMetadata> Endpoints
{get;}
//More members
}

每个端点都由 EndpointDiscoveryMetadata 类表示:

public class EndpointDiscoveryMetadata
{
public EndpointAddress Address
{get;set;}
//More members
}

EndpointDiscoveryMetadata 的主要属性是地址,该属性就包含发现的端点和地址,客户端需用的就是如何结合使用这些类型以发现端点地址并调用服务。

DiscoveryClient discoveryClient = 
new DiscoveryClient(new UdpDiscoveryEndpoint());
FindCriteria criteria = new FindCriteria(typeof(IMyContract));
FindResponse discovered = discoveryClient.Find(criteria);
discoveryClient.Close();
//Just grab the first found
EndpointAddress address = discovered.Endpoints[0].Address;
Binding binding = new NetTcpBinding();
IMyContract proxy =
ChannelFactory<IMyContract>.CreateChannel(binding,address);
proxy.MyMethod();
(proxy as ICommunicationObject).Close();

至此我们已经实现了一个wcf服务端的发布和客户端的嗅探工作。

客户端可能找到发现服务多个支持所需约定的端点,但它不具有逻辑分析功能以确定应该调用哪个端点。只调用返回的集合中的第一个端点。当然我们可以加中介的方式显示的为其逻辑上分析功能、预热等工作。

发现仅用于地址发现。其中未提供有关使用哪个绑定以调用服务的信息。以上只是对 TCP 绑定的使用进行了硬编码。每当客户端需要发现服务地址时,不得不反复重复这些极小的步骤。

案例测试

通过上面的分析,下面我们写一个详细的案例:

第一步:新建方案ConsoleApplicationWCFDiscovery,定义服务契约ICalculator,参照蒋老师的的程序:

using System.ServiceModel;
namespace ConsoleApplicationWCFDiscovery
{
[ServiceContract(Namespace = "http://www.wuxuelei.com", Name="MySever")]
public interface ICalculator
{
[OperationContract]
double Add(double x, double y);

}
}

第二步、实现服务契约:

using System;

namespace ConsoleApplicationWCFDiscovery
{
public class CalculatorService:ICalculator
{
public double Add(double x, double y)
{
return x + y;
}
}
}

第三步、定义服务配置文件:

<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<behaviors>
<!--定义服务端服务-->
<serviceBehaviors>
<behavior>
<!--定义服务为可发现型-->
<serviceDiscovery/>
</behavior>
</serviceBehaviors>

<!--定义发现服务的查找地址-->
<endpointBehaviors>
<behavior name="MyMapping">
<endpointDiscovery enabled="true">
<scopes>
<add scope="http://www.wuxuelei.com/caculatorservice"/>
</scopes>
</endpointDiscovery>
</behavior>
</endpointBehaviors>
</behaviors>

<!--被寄宿的终结点除了有一个基于WS2007HttpBinding的终结点外,还具有另一个UdpDiscoveryEndpoint标准节点
定义了一个MyMapping的终结点行为,该行为通过EndpointDiscoveryBehavior行为定义一个代表服务范围。最后我们
定义了一个默认的服务行为,而ServiceDiscoveryBehavior被定义其中。现在被寄宿的服务具有了ServiceDiscoveryBehavior行为
和一个UdpDisvoveryEndpoint,所以他就是一个被发现的的服务了。最后,该服务通过一段简单的程序自寄宿-->
<!--定义终结点-->
<services>
<service name="ConsoleApplicationWCFDiscovery.CalculatorService">
<endpoint address="http://127.0.0.1:3721/Calculator"
binding="ws2007HttpBinding"
contract="ConsoleApplicationWCFDiscovery.ICalculator"
behaviorConfiguration="MyMapping"/>
<!--采取UDP广播协议-->
<endpoint kind="udpDiscoveryEndpoint"/>
</service>
</services>

</system.serviceModel>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

注释很详细,不解释

第三步、托管服务,我们采取自托管的方式:

using System;
using System.ServiceModel;

namespace ConsoleApplicationWCFDiscovery
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
{
host.Opened += delegate
{
Console.WriteLine("服务已经启动!");
};
host.Open();
Console.Read();
}
}
}
}

以上是服务端的发布,下面我们实现客户端的嗅探工作:

using System;
using System.ServiceModel;
//新建命令行
using System.ServiceModel.Discovery;
using ConsoleApplicationWCFDiscovery;
using ConsoleApplicationWCFDiscovery2;
namespace 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.wuxuelei.com"));
//获取相应的回应
FindResponse response = discoveryClient.Find(criteria);
//判断回应内容,如果回应的匹配条数大于0
if (response.Endpoints.Count > 0)
{
foreach (EndpointDiscoveryMetadata endpint in response.Endpoints)
{
//获取该服务地址
EndpointAddress address = endpint.Address;
Console.WriteLine(string.Format("发现服务:{0}", address.Uri.ToString()));
if (address.Uri.ToString().Substring(address.Uri.ToString().LastIndexOf('/')+1).Equals("Calculator"))
CreateWcfClient(ClassType.type1, address);
else
CreateWcfClient(ClassType.type2, address);
}

}
//关闭嗅探
discoveryClient.Close();
Console.ReadLine();
}

public enum ClassType
{
//加法
type1 = 0,
//减法
type2 = 1,
}
static void CreateWcfClient(ClassType ct, EndpointAddress _address)
{
if (ct==ClassType.type1)
using (var 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));
}
else
using (var ChannelFactory = new ChannelFactory<Iminus>(new WS2007HttpBinding(), _address))
{
Iminus calculator = ChannelFactory.CreateChannel();
Console.WriteLine("x-y={2} when x={0} and y={1}", 5, 3, calculator.myMinus(5, 3));
}
}

}
}

运行程序:

可以看到,该服务我们是可以嗅探到的,并且我们输出了该发现服务中所包含的服务地址,我们使用的嗅探服务的地址都是:http://www.wuxuelei.com也就是说我们得分服务都是从这个地址上去嗅探,我们再新建另一个发现服务,用同样的域名去发布,看看我们的客户端是否也能嗅探到:

看我们的发现服务配置文件

<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<!--定义服务端服务-->
<behaviors>
<serviceBehaviors>
<behavior>
<!--定义服务为可发现型-->
<serviceDiscovery/>
</behavior>
</serviceBehaviors>
<!--定义服务地址-->
<endpointBehaviors>
<behavior name="myMapping">
<endpointDiscovery enabled="true">
<scopes>
<!--定义第二个服务地址-->
<add scope="http://www.wuxuelei.com/"/>
</scopes>
</endpointDiscovery>
</behavior>
</endpointBehaviors>
</behaviors>
<!--定义服务契约-->
<services>
<service name="ConsoleApplicationWCFDiscovery2.minus">
<endpoint address="http://127.0.0.1:3722/minus"
binding="ws2007HttpBinding"
contract="ConsoleApplicationWCFDiscovery2.Iminus"
behaviorConfiguration="myMapping"/>
<endpoint kind="udpDiscoveryEndpoint"/>
</service>
</services>

</system.serviceModel>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

这里有要注意,就是端口不能使用相同域名下的同一端口,看以看到我们的这个服务同样的也是发布在http://www.wuxuelei.com域名下的,我们同时运行两个服务:

 看以看到两个服务我们能同时的嗅探的到,呵呵,并成功运行

当然这种方式也不是万能,同样的有他自有的局限性:

发现服务需要花费时间。默认情况下,Find 方法将等待 20 秒以获取响应 UDP 发现请求的服务。这样的延迟使得发现不适用于许多应用程序,特别是应用程序执行大量紧密调用时。您可以缩短该超时,但如果这么做,可能会无法发现部分或所有服务。DiscoveryClient 不提供异步发现,但异步发现不适用于需要调用服务后才可以继续执行的客户端。 当然我们能够自己能采取相应的方式进行优化。

当然这里面还有域的管理和分配,服务的上下线操作,可以关注蒋老师的博客,他对其进行了分析。后续.....

原文地址:https://www.cnblogs.com/zhijianliutang/p/2299457.html