[WCF-Discovery]如何利用”发现代理”实现可用服务的实时维护?

上面的内容大部分是围绕着Ad-Hoc模式展开介绍的。Managed模式和Ad-Hoc不同之处在于可用服务的终结点通过发现代理来统一管理。客户端在进行可用目标服务探测和解析的时候不再需要发送广播请求,而是直接向发现代理进行探测和解析请求就可以了。[源代码从这里下载]

目录
一、发现代理与Managed发现模式
二、通过继承DiscoveryProxy创建发现代理
三、实例演示:自定义发现代理服务
    步骤一、创建自定义发现代理服务
    步骤二、寄宿发现代理服务和目标服务
    步骤三、服务的动态调用

一、发现代理与Managed发现模式

至于发现服务如何进行可用服务的实时维护,则是具体实现上的选择问题。不过WS-Discovery通过目标服务的通知机制来解决发现代理维护的服务的实时可用性。具体来说就是赋予了发现代理监听服务上下线通知的能力,并根据接收到的通知来进行可用服务的动态注册和注销。不过与Ad-Hoc模式下采用广播模式的通知不同,在Managed模式下,目标服务只需要专门针对发现代理发送通知就可以了。

在Ad-Hoc模式下,我们采用UdpAnnouncementEndpoint实现了广播式的通知,而在Managed模式则直接使用AnnouncementEndpoint终结点进行单播式的通知。该终结点的地址就是发现代理的地址。同理,在Ad-Hoc模式下我们进行广播式服务探测和解析是通过UdpDiscoveryEndpoint终结点来进行的,在Managed模式下我们可以直接使用DiscoveryEndpoint终结点实现客户端向发现代理单方面的可用服务的探测和解析请求。

发现代理部仅仅局限于Managed模式,同样可以使用在Ad-Hoc模式下。在Ad-Hoc模式下,发现代理可以像目标服务一样监听来自客户端发出的广播式的Probe/Resolve请求,也可以像客户端一样监听来自服务端发出的广播式的Helle/Bye通知。所以UdpDiscoveryEndpoint和UdpAnnouncementEndpoint同样可以应用在发现代理上。

发现代理本质上就是一个服务,它的核心功能就是接收客户端发送的针对可用服务探测和解析的Probe/Resolve请求,并回复以相应的PM和RM消息。至于上面提到的对目标服务上/下线通知监听能力只是具体实现对可用服务维护的一种方式而已。

二、通过继承DiscoveryProxy创建发现代理

发现服务本质上就是一个WCF服务,并且这个服务实现的服务契约定义的操作应该基于定义在WS-Discovery中的几种基本的消息交换:Probe/PM、Resolve/RM和Hello/Bye。交换的消息在针对不同版本的WS-Discovery(WSDiscoveryApril2005、WSDiscovery11和WSDiscoveryCD1)又具有不同的要求。即使针对某个具体版本的WS-Discovery,Probe/PM和Resolve/RM的消息也会因采用Ad-Hoc或者Managed模式又有所不同。如果你需要创建一个同时支持不同版本WS-Discovery的发现代理服务,就应该实现DiscoveryEndpoint和AnnouncementEndpoint终结点所实现的所有服务契约。

所以说要自己从头到尾去定义这么一个发现代理服务并不是一件容易的事情。为了使开发人员可以无需关注具体的消息交换的细节,帮助他们容易的定义发现代理,WCF提供了一个抽象类DiscoveryProxy。我们只需要将我们自定义的发现代理服务类型继承该类并且重写相应的方法就可以了。

下面的代码给出了DiscoveryProxy的核心方法的定义。正如我们上面的分析,作为一个完备的发现代理服务应该实现DiscoveryEndpoint和AnnouncementEndpoint终结点所实现的所有服务契约,在这里得到了证实。DiscoveryProxy定义了4组抽象的OnBegingXxx/OnEndXxx方法,分别针四个基本的服务发现操作(消息交换):服务探测(Probe/PM)、服务解析(Resolve/RM)、上线通知(Hello)和离线通知(Bye)。作为继承自DiscoveryProxy的自定义发现代理服务,只需要重写这些抽象方法既可。

   1: public abstract class DiscoveryProxy : 
   2:     IAnnouncementContractApril2005, 
   3:     IAnnouncementContract11, 
   4:     IAnnouncementContractCD1, 
   5:     IDiscoveryContractAdhocApril2005, 
   6:     IDiscoveryContractManagedApril2005, 
   7:     IDiscoveryContractApril2005, 
   8:     IDiscoveryContractAdhoc11, 
   9:     IDiscoveryContractManaged11, 
  10:     IDiscoveryContractAdhocCD1, 
  11:     IDiscoveryContractManagedCD1, ...
  12: {
  13:     //Find(Probe)   
  14:     protected abstract IAsyncResult OnBeginFind(FindRequestContext findRequestContext, AsyncCallback callback, object state);
  15:     protected abstract void OnEndFind(IAsyncResult result);
  16:     
  17:     //Resolve
  18:     protected abstract IAsyncResult OnBeginResolve(ResolveCriteria resolveCriteria, AsyncCallback callback, object state);
  19:     protected abstract EndpointDiscoveryMetadata OnEndResolve(IAsyncResult result);
  20:  
  21:     //Online Announcement(Hello)
  22:     protected abstract IAsyncResult OnBeginOnlineAnnouncement(DiscoveryMessageSequence messageSequence, EndpointDiscoveryMetadata 
  23:     endpointDiscoveryMetadata, AsyncCallback callback, object state);
  24:     protected abstract void OnEndOnlineAnnouncement(IAsyncResult result);
  25:  
  26:     //Offline Announcement(Bye)
  27:     protected abstract IAsyncResult OnBeginOfflineAnnouncement(DiscoveryMessageSequence messageSequence, EndpointDiscoveryMetadata 
  28:     endpointDiscoveryMetadata, AsyncCallback callback, object state);    
  29:     protected abstract void OnEndOfflineAnnouncement(IAsyncResult result);   
  30:     
  31:     //其他成员
  32: }

三、实例演示:自定义发现代理服务

接下来我们将通过一个简单的实例演示如何自定义发现代理服务,以及如何利用这个发现代理构建一个基于Managed模式的服务发现环境以实现服务的自动注册和服务的动态调用。实例解决方法依然采用之前的结构,并且直接使用定义好的CalculatorService作为目标服务。

步骤一、创建自定义发现代理服务

我们首先通过继承DiscoveryProxy创建一个自定义的发现代理服务,我们将它起名为DiscoveryProxyService。由于我们要重写的方法都是异步模式的,OnBeginXxx的输出和OnEndXxx的输入都是一个IAsyncResult类型的对象,所以我们先要定义一个实现IAsyncResult接口的类型。为了简单起见,我们在Servie项目中定义的如下一个最为简单的DiscoveryAsyncResult(其实它根本起不到异步执行的目的)。

   1: using System;
   2: using System.ServiceModel.Discovery;
   3: using System.Threading;
   4: namespace Artech.WcfServices.Service
   5: {
   6:     public class DiscoveryAsyncResult : IAsyncResult
   7:     {
   8:         public object AsyncState { get; private set; }
   9:         public WaitHandle AsyncWaitHandle { get; private set; }
  10:         public bool CompletedSynchronously { get; private set; }
  11:         public bool IsCompleted { get; private set; }
  12:         public EndpointDiscoveryMetadata Endpoint { get; private set; }
  13:  
  14:         public DiscoveryAsyncResult(AsyncCallback callback, object asyncState)
  15:         {
  16:             this.AsyncState = asyncState;
  17:             this.AsyncWaitHandle = new ManualResetEvent(true);
  18:             this.CompletedSynchronously = this.IsCompleted = true;
  19:             if (callback != null)
  20:             {
  21:                 callback(this);
  22:             }
  23:         }
  24:         public DiscoveryAsyncResult(AsyncCallback callback, object asyncState, 
  25:             EndpointDiscoveryMetadata Endpoint)
  26:             : this(callback, asyncState)
  27:         {
  28:             this.Endpoint = Endpoint;
  29:         }
  30:     }
  31: }

我们来创建我们自定义如下一个发现代理服务DiscoveryProxyService,我们通过在类型上应用ServiceBehaviorAttribute特性将DiscoveryProxyService定义成一个单例服务,并且支持并发。

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.ServiceModel;
   5: using System.ServiceModel.Discovery;
   6: namespace Artech.WcfServices.Service
   7: {
   8:     [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,ConcurrencyMode = ConcurrencyMode.Multiple)]
   9:     public class DiscoveryProxyService : DiscoveryProxy
  10:     {
  11:         public IDictionary<EndpointAddress, EndpointDiscoveryMetadata> Endpoints { get; private set; }
  12:         public DiscoveryProxyService()
  13:         {
  14:             this.Endpoints = new Dictionary<EndpointAddress, EndpointDiscoveryMetadata>();
  15:         }
  16:  
  17:         //Find(Probe)
  18:         protected override IAsyncResult OnBeginFind(FindRequestContext findRequestContext, AsyncCallback callback, object state)
  19:         {
  20:             var endpoints = from item in this.Endpoints
  21:                             where findRequestContext.Criteria.IsMatch(item.Value)
  22:                             select item.Value;
  23:             foreach (var endppint in endpoints)
  24:             {
  25:                 findRequestContext.AddMatchingEndpoint(endppint);
  26:             }
  27:             return new DiscoveryAsyncResult(callback, state);
  28:         }
  29:         protected override void OnEndFind(IAsyncResult result) {}
  30:  
  31:         //Resolve
  32:         protected override IAsyncResult OnBeginResolve(ResolveCriteria resolveCriteria, AsyncCallback callback, object state)
  33:         {
  34:             EndpointDiscoveryMetadata endpoint = null;
  35:             if (this.Endpoints.ContainsKey(resolveCriteria.Address))
  36:             {
  37:                 endpoint = this.Endpoints[resolveCriteria.Address];
  38:             }
  39:             return new DiscoveryAsyncResult(callback, endpoint);
  40:         }
  41:         protected override EndpointDiscoveryMetadata OnEndResolve(IAsyncResult result)
  42:         {
  43:             return ((DiscoveryAsyncResult)result).Endpoint;
  44:         }
  45:  
  46:         //OnlineAnnouncement
  47:         protected override IAsyncResult OnBeginOnlineAnnouncement(DiscoveryMessageSequence messageSequence, EndpointDiscoveryMetadata 
  48:             endpointDiscoveryMetadata, AsyncCallback callback, object state)
  49:         {
  50:             this.Endpoints[endpointDiscoveryMetadata.Address] = endpointDiscoveryMetadata;
  51:             return new DiscoveryAsyncResult(callback, state);
  52:         }
  53:         protected override void OnEndOnlineAnnouncement(IAsyncResult result) {}
  54:  
  55:         //OfflineAnnouncement
  56:         protected override IAsyncResult OnBeginOfflineAnnouncement(DiscoveryMessageSequence messageSequence, EndpointDiscoveryMetadata 
  57:             endpointDiscoveryMetadata, AsyncCallback callback, object state)
  58:         {
  59:             if (this.Endpoints.ContainsKey(endpointDiscoveryMetadata.Address))
  60:             {
  61:                 this.Endpoints.Remove(endpointDiscoveryMetadata.Address);
  62:             }
  63:             return new DiscoveryAsyncResult(callback, state);
  64:         }
  65:         protected override void OnEndOfflineAnnouncement(IAsyncResult result) {}
  66:     }
  67: }

DiscoveryProxyService具有个IDictionary<EndpointAddress, EndpointDiscoveryMetadata>类型的属性Endpoints表述可用的目标服务列表。在处理服务上线通知的OnBeginOnlineAnnouncemen/OnEndOnlineAnnouncement方法中讲代表上线服务的EndpointDiscoveryMetadata添加到Endpoints列表中。而在处理服务离线通知的OnBeginOfflineAnnouncement/OnEndOfflineAnnouncement方法中则将代表离线服务的EndpointDiscoveryMetadata从Endpoints列表中移除。

而处理客户端服务探测请求的OnBeginFind/OnEndFind方法中,从传入的FindRequestContext中获得代表匹配条件的FindCriteria对象,并通过它从Endpoints列表中找到匹配的EndpointDiscoveryMetadata,最终通过调用的AddMatchingEndpoint方法将它们添加到FindRequestContext之中。至于用于处理服务解析请求的OnBeginResolve/ OnEndResolve则只需要从Endpoints列表中将与给定的终结点地址一致的EndpointDiscoveryMetadata返回就可以了。

步骤二、寄宿发现代理服务和目标服务

现在我们需要寄宿上面创建的自定义发现代理服务DiscoveryProxyService和代表目标服务的CalculatorService,我们把所有的设置都定义在如下的配置中。

   1: <configuration>
   2:   <system.serviceModel>
   3:     <services>
   4:       <service name="Artech.WcfServices.Service.DiscoveryProxyService">
   5:         <endpoint address="net.tcp://127.0.0.1:8888/discoveryproxy/probe" 
   6:                   binding="netTcpBinding" 
   7:                   kind="discoveryEndpoint" 
   8:                   isSystemEndpoint="false" />
   9:         <endpoint address="net.tcp://127.0.0.1:9999/discoveryproxy/announcement" 
  10:                   binding="netTcpBinding" 
  11:                   kind="announcementEndpoint"/>
  12:       </service>
  13:       <service name="Artech.WcfServices.Service.CalculatorService" 
  14:                behaviorConfiguration="serviceAnnoucement">
  15:         <endpoint address="http://127.0.0.1:3721/calculatorservice" 
  16:                   binding="ws2007HttpBinding" 
  17:                   contract="Artech.WcfServices.Service.Interface.ICalculator" />
  18:       </service>
  19:     </services>
  20:     <behaviors>
  21:       <serviceBehaviors>
  22:         <behavior>
  23:           <serviceDiscovery/>
  24:         </behavior>
  25:         <behavior name="serviceAnnoucement">
  26:           <serviceDiscovery>
  27:             <announcementEndpoints>
  28:               <endpoint kind="announcementEndpoint"
  29:                         address="net.tcp://127.0.0.1:9999/discoveryproxy/announcement" 
  30:                         binding="netTcpBinding"/>
  31:             </announcementEndpoints>
  32:           </serviceDiscovery>
  33:         </behavior>
  34:       </serviceBehaviors>
  35:     </behaviors>
  36:   </system.serviceModel>
  37: </configuration>

首先,一个包含ServiceDiscoveryBehavior的默认服务行为被定义,它将会自动应用到寄宿的两个服务上。对于发现代理服务DiscoveryProxyService,它具有两个采用NetTcpBinding绑定的标准终结点。其中一个地址为“net.tcp://127.0.0.1:8888/discoveryproxy/probe”,isSystemEndpoint属性被设置成False(这个设置是必需的)的DiscoveryEndpoint终结点。另一个则是地址为“net.tcp://127.0.0.1:9999/discoveryproxy/announcement”的AnnouncementEndpoint终结点。

至于目标服务CalculatorService,应用了一个名称为serviceAnnoucement的服务行为。通过这个服务行为为它添加了一个AnnouncementEndpoint终结点。该终结点采用NetTcpBinding,而地址则是发现代理服务AnnouncementEndpoint终结点的地址“net.tcp://127.0.0.1:9999/discoveryproxy/announcement”。

然后我们通过如下一段简单的代码来同时寄宿发现代理服务DiscoveryProxyService和目标服务CalculatorService。由于目标服务CalculatorService是在发现代理服务之后开启,所以在它开启之后会自动向发现服务发送一个上线的通知,而发现代理在接收到通知之后会将目标服务的EndpointDiscoveryMetadata添加到Endpoints列表中。

   1: using System;
   2: using System.ServiceModel;
   3: namespace Artech.WcfServices.Service
   4: {
   5:     class Program
   6:     {
   7:         static void Main(string[] args)
   8:         {
   9:             using(ServiceHost  discoveryProxyService = new ServiceHost(typeof(DiscoveryProxyService)))
  10:             using (ServiceHost calculatorService = new ServiceHost(typeof(CalculatorService)))
  11:             {
  12:                 discoveryProxyService.Open();
  13:                 calculatorService.Open();
  14:                 Console.Read();
  15:             }
  16:         }
  17:     }
  18: }

步骤三、服务的动态调用

现在我们需要让客户端在不知道目标服务终结点地址的情况下进行服务的动态调用。我们直接使用DynamicEndpoint标准终结点。下面的XML片断代表客户端程序的配置,在这段配置中定义了唯一一个用于调用CalclatorService的DynamicEndpoint终结点。为了让这个DynamicEndpoint终结点通过请求我们寄宿的发现代理服务进行了可用服务的探测,我们为它添加了一个采用NetTcpBindg的DiscoveryEndpoint终结点,该终结点的地址为“net.tcp://127.0.0.1:8888/discoveryproxy/probe”。

   1: <configuration>
   2:   <system.serviceModel>
   3:     <client>
   4:       <endpoint name="calculatorService" 
   5:                 kind="dynamicEndpoint" 
   6:                 endpointConfiguration="unicastEndpoint" 
   7:                 binding="ws2007HttpBinding" 
   8:                 contract="Artech.WcfServices.Service.Interface.ICalculator"/>
   9:     </client>
  10:     <standardEndpoints>
  11:       <dynamicEndpoint>
  12:         <standardEndpoint name="unicastEndpoint">
  13:           <discoveryClientSettings>
  14:             <endpoint kind="discoveryEndpoint" 
  15:                       address="net.tcp://127.0.0.1:8888/discoveryproxy/probe" 
  16:                       binding="netTcpBinding"/>
  17:           </discoveryClientSettings>
  18:         </standardEndpoint>
  19:       </dynamicEndpoint>
  20:     </standardEndpoints>
  21:   </system.serviceModel>
  22: </configuration>

而真正服务调用的代码和调用普通服务没有两样。完成所有的配置和编码工作之后,先后启动服务和客户端程序,你会发现客户端控制台具有如下的输出结果,表示服务调用成功完成。

   1: using System;
   2: using System.ServiceModel;
   3: using Artech.WcfServices.Service.Interface;
   4: namespace Artech.WcfServices.Client
   5: {
   6:     class Program
   7:     {
   8:         static void Main(string[] args)
   9:         {
  10:             using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService"))
  11:             {
  12:                 ICalculator calculator = channelFactory.CreateChannel();
  13:                 Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));
  14:             }
  15:             Console.Read();
  16:         }
  17:     }
  18: }

输出结果:

   1: x + y = 3 when x = 1 and y = 2

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
上一篇:使用体验感


下一篇:对 ar 命令的学习