閱讀811 返回首頁    go 阿裏雲 go 技術社區[雲棲]


[WCF-Discovery]如何利用”發現代理”實現可用服務的實時維護?

上麵的內容大部分是圍繞著Ad-Hoc模式展開介紹的。Managed模式和Ad-Hoc不同之處在於。客戶端在進行可用目標服務探測和解析的時候不再需要發送廣播請求,而是直接向發現代理進行探測和解析請求就可以了。[源代碼從這裏下載]

目錄
一、發現代理與Managed發現模式
二、通過繼承DiscoveryProxy創建發現代理
三、實例演示:自定義發現代理服務
    步驟一、創建自定義發現代理服務
    步驟二、寄宿發現代理服務和目標服務
    步驟三、服務的動態調用

至於發現服務如何進行可用服務的實時維護,則是具體實現上的選擇問題。不過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消息。至於上麵提到的對目標服務上/下線通知監聽能力隻是具體實現對可用服務維護的一種方式而已。

發現服務本質上就是一個WCF服務,並且這個服務實現的服務契約定義的操作應該基於定義在WS-Discovery中的幾種基本的消息交換:。交換的消息在針對不同版本的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="https://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
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 14:34:40

  上一篇:go  [WCF-Discovery]讓服務自動發送上/下線通知[實例篇]
  下一篇:go  WCF的安全審核——記錄誰在敲打你的門