[WCF-Discovery]讓服務自動發送上/下線通知[原理篇]
到目前為止,我們所介紹的都是基於客戶端驅動的服務發現模式,也就是說客戶端主動發出請求以探測和解析可用的目標服務。在介紹WS-Discovery的時候,我們還談到另外一種服務驅動的模式,即服務在上線和下線的時候主動對外發出Hello/Bye通知。服務上下線通知機製依賴另外一個AnnouncementEndpoint標準終結點。
目錄
AnnouncementEnpoint
UdpAnnouncementEnpoint
上下線通知的發送
上下線通知的接收
在采用服務端驅動的情況下,目標服務通過AnnouncementEndpoint終結點發送上下線通知,而客戶端通過相同的終結點接收通知。和DiscoveryEndpoint終結點一樣,AnnouncementEndpoint終結點也基於WS-Discovery標準的。具體來說,前者完成Probe/PM和Resolve/RM消息交換,而後者則實現Hello/Bye消息交換。
下麵的代碼片斷給出了AnnouncementEndpoint定義。和DiscoveryEndpoint終結點一樣,我們隻需要指定終結點ABC三要素的地址和綁定來創建AnnouncementEndpoint,而契約決定於采用的WS-Discovery的版本(與采用Ad-Hoc/Managed模式無關)。WS-Discovery的版本通過屬性DiscoveryVersion表示,在默認的情況下采用的版本為WS-Discovery 1.1。
1: public class AnnouncementEndpoint : ServiceEndpoint
2: {
3: //其他成員
4: public AnnouncementEndpoint(DiscoveryVersion discoveryVersion);
5: public AnnouncementEndpoint(Binding binding, EndpointAddress address);
6: public AnnouncementEndpoint(DiscoveryVersion discoveryVersion, Binding binding, EndpointAddress address);
7:
8: public DiscoveryVersion DiscoveryVersion { get; }
9: public TimeSpan MaxAnnouncementDelay { get; set; }
10: }
另一個屬性MaxAnnouncementDelay與DiscoveryEndpoint的MaxResponseDelay屬性的作用類似,通過它設置一個最大允許的通知延遲發送的時間跨度,以防止在網絡出現故障後所有服務同時重新聯機所造成的網絡風暴。MaxAnnouncementDelay屬性的默認值為“00:00:00”,意味著通知在服務上/下線的時候會被立即發送出去。
我們不妨采用之前分析DiscoveryEndpoint終結點的方式來分析一下AnnouncementEndpoint終結點在選擇不同的WS-Discovery版本的情況下具有怎樣的契約。為此我們編寫了如下一段測試程序,該程序基於三種不同的版本(WSDiscoveryApril2005、WSDiscovery11和WSDiscoveryCD1)分別創建AnnouncementEndpoint,最終將該終結地契約的類型名稱輸出來。
1: AnnouncementEndpoint endpoint;
2: endpoint = new AnnouncementEndpoint(DiscoveryVersion.WSDiscoveryApril2005);
3: Console.WriteLine("{0,-20}: {1}", "WSDiscoveryApril2005", endpoint.Contract.ContractType.Name);
4:
5: endpoint = new AnnouncementEndpoint(DiscoveryVersion.WSDiscovery11);
6: Console.WriteLine("{0,-20}: {1}", "WSDiscovery11", endpoint.Contract.ContractType.Name);
7:
8: endpoint = new AnnouncementEndpoint(DiscoveryVersion.WSDiscoveryCD1);
9: Console.WriteLine("{0,-20}: {1}", "WSDiscoveryCD1", endpoint.Contract.ContractType.Name);
輸出結果:
1: WSDiscoveryApril2005 : IAnnouncementContractApril2005
2: WSDiscovery11 : IAnnouncementContract11
3: WSDiscoveryCD1 : IAnnouncementContractCD1
從輸出結果我們可以看到,基於指定的三種不同的WS-Discovery版本,AnnouncementEndpoint終結點契約類型分別為IAnnouncementContractApril2005、IAnnouncementContract11和IAnnouncementContractCD1。它們實際上對應著相應的內部接口,並不對外公布。不過為了更加深刻地認識AnnouncementEndpoint終結點,我們不妨來看看基於WS-Discovery 1.1的契約接口IAnnouncementContract11的定義。
1: [ServiceContract(Name = "Client", Namespace = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01")]
2: internal interface IAnnouncementContract11
3: {
4: //Hello
5: [OperationContract(IsOneWay = true, Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Hello")]
6: void HelloOperation(HelloMessage11 message);
7: [OperationContract(IsOneWay = true, Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Hello", AsyncPattern = true)]
8: IAsyncResult BeginHelloOperation(HelloMessage11 message, AsyncCallback callback, object state);
9: void EndHelloOperation(IAsyncResult result);
10:
11: //Bye
12: [OperationContract(IsOneWay = true, Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Bye")]
13: void ByeOperation(ByeMessage11 message);
14: [OperationContract(IsOneWay = true, Action = "https://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Bye", AsyncPattern = true)]
15: IAsyncResult BeginByeOperation(ByeMessage11 message, AsyncCallback callback, object state);
16: void EndByeOperation(IAsyncResult result);
17: }
由於AnnouncementEndpoint終結點旨在實現定義在WS-Discovery消息交換模型中的部分,所以IAnnouncementContract11僅僅包含兩套操作。其中ByeOperation(同步)和BeginHelloOperation/EndHelloOperation(異步)基於服務上線的Hello通知,而ByeOperation(同步)和BeginByeOperation/EndByeOperation(異步)基於服務離線的Bye通知。由於通知都是單向的,所以兩個操作的IsOneWay屬性為True。服務契約的命名空間、操作的Action的值都與WS-Discovery 1.1規範一致。
AnnouncementEndpoint的兩個屬性DiscoveryVersion和MaxAnnouncementDelay都可以通過它的標準終結點配置元素相應的屬性(discoveryVersion和maxAnnouncementDelay)來指定。在下麵的配置中,我們定義了一個名稱為endpoint4April2005的AnnouncementEndpoint終結點,它基於WS-Discovery April 2005,最大通知延遲的時間為20秒。
1: <configuration>
2: <system.serviceModel>
3: <standardEndpoints>
4: <announcementEndpoint>
5: <standardEndpoint name="endpoint4April2005"
6: discoveryVersion="WSDiscoveryApril2005"
7: maxAnnouncementDelay="00:00:20" />
8: </announcementEndpoint>
9: </standardEndpoints>
10: </system.serviceModel>
11: </configuration>
由於AnnouncementEndpoint終結點需要指定具體的地址,所以它采用的是單播的模式。為了支持廣播模式的通知,WCF為AnnouncementEndpoint設計了基於UDP的版本,即UdpAnnouncementEndpoint標準終結點。
從下麵的代碼片斷中我們不難發現,UdpAnnouncementEndpoint和之前介紹的UdpDiscoveryEndpoint具有相同的屬性定義。它們都包括具有一個表示廣播地址的MulticastAddress屬性,而另一個屬性TransportSettings用於進行UDP傳輸層設置。正因為如此,UdpAnnouncementEndpoint和UdpDiscoveryEndpoint這兩個基於UDP傳輸協議的標準終結點具有相同結構的配置(參考《[WCF-Discovery]服務如何能被”發現”》)。
1: public class UdpAnnouncementEndpoint : AnnouncementEndpoint
2: {
3: //其他成員
4: public Uri MulticastAddress { get; set; }
5: public UdpTransportSettings TransportSettings {get; }
6: }
接下來我們關注另外一個主題:?這自然要使用到上麵我們介紹的AnnouncementEndpoint終結點了。但是,是否我們隻需要在服務寄宿的時候為寄宿的服務添加這樣一個標準終結點就可以了呢?實則不然。
最終使服務具有通知發送功能的AnnouncementEndpoint終結點實際上是通過一個被應用到目標服務上的,而這個服務行為就是我們之前介紹過的ServiceDiscoveryBehavior。它除了通過實現發現服務的激活以使目標服務可以被探測和解析之外,還可以為目標服務添加一到多個AnnouncementEndpoint終結點使在上/下線的時候對外發出通知。從下麵給出的代碼片斷來看,ServiceDiscoveryBehavior具有一個類型為AnnouncementEndpoint集合的隻讀屬性AnnouncementEndpoints。
1: public class ServiceDiscoveryBehavior : IServiceBehavior
2: {
3: //其他成員
4: public Collection<AnnouncementEndpoint> AnnouncementEndpoints { get; }
5: }
如果采用編程的方式來應用這個服務行為,你可以手工地將創建的AnnouncementEndpoint終結點添加到ServiceDiscoveryBehavior的AnnouncementEndpoints集合中。我們還是推薦采用配置的方式來為服務添加AnnouncementEndpoint終結點。在ServiceDiscoveryBehavior對應的配置節下具有一個<announcementEndpoints>子節點用於配置AnnouncementEndpoint列表。在下麵的配置中,我們定義了一個包含ServiceDiscoveryBehavior的默認服務行為,它的AnnouncementEndpoints集合中包含一個自定義的UdpAnnouncementEndpoint終結點。
1: <configuration>
2: <system.serviceModel>
3: <behaviors>
4: <serviceBehaviors>
5: <behavior>
6: <serviceDiscovery>
7: <announcementEndpoints>
8: <endpoint kind="udpAnnouncementEndpoint"
9: endpointConfiguration="endpoint4April2005"/>
10: </announcementEndpoints>
11: </serviceDiscovery>
12: </behavior>
13: </serviceBehaviors>
14: </behaviors>
15: <standardEndpoints>
16: <announcementEndpoint>
17: <standardEndpoint name="endpoint4April2005"
18: discoveryVersion="WSDiscoveryApril2005"
19: maxAnnouncementDelay="00:00:20" />
20: </announcementEndpoint>
21: </standardEndpoints>
22: </system.serviceModel>
23: </configuration>
通過ServiceDiscoveryBehavior為目標服務添加相應的AnnouncementEndpoint終結點是它具有了自動發送上/下線通知的能力。實際上除了這種自動的方式之外,我們可以“手動”地進行通知的發送,這就需要使用到另外一個具有如下定義的 AnnouncementClient。
1: public sealed class AnnouncementClient : ICommunicationObject, IDisposable
2: {
3: //其他成員
4: public event EventHandler<AsyncCompletedEventArgs> AnnounceOnlineCompleted;
5: public event EventHandler<AsyncCompletedEventArgs> AnnounceOfflineCompleted;
6:
7: public AnnouncementClient();
8: public AnnouncementClient(AnnouncementEndpoint announcementEndpoint);
9: public AnnouncementClient(string endpointConfigurationName);
10:
11: //AnnounceOnline
12: public void AnnounceOnline(EndpointDiscoveryMetadata discoveryMetadata);
13: public void AnnounceOnlineAsync(EndpointDiscoveryMetadata discoveryMetadata);
14: public void AnnounceOnlineAsync(EndpointDiscoveryMetadata discoveryMetadata, object userState);
15: public IAsyncResult BeginAnnounceOnline(EndpointDiscoveryMetadata discoveryMetadata, AsyncCallback callback, object state);
16: public void EndAnnounceOnline(IAsyncResult result);
17:
18: //AnnounceOffline
19: public void AnnounceOffline(EndpointDiscoveryMetadata discoveryMetadata);
20: public void AnnounceOfflineAsync(EndpointDiscoveryMetadata discoveryMetadata);
21: public void AnnounceOfflineAsync(EndpointDiscoveryMetadata discoveryMetadata, object userState);
22: public IAsyncResult BeginAnnounceOffline(EndpointDiscoveryMetadata discoveryMetadata, AsyncCallback callback, object state);
23: public void EndAnnounceOffline(IAsyncResult result);
24: }
,所以我們在調用構造函數來創建AnnouncementClient的時候需要指定一個具體的AnnouncementEndpoint對象或者置名稱。如果調用無參構造函數,則要求默認的客戶端終結點必須是AnnouncementEndpoint終結點。
AnnouncementClient具有兩套分別用於發送上線和離線通知的方法,方法的輸入都是包含被通知服務相關元數據的EndpointDiscoveryMetadata對象。其中AnnounceOnline/ AnnounceOffline通過同步的方式實現上/下線通知的發送,而異步方式則具有兩個方式:一種是傳統的Beging/End的方式,而另一種通過調用AnnounceOnlineAsync/ AnnounceOfflineAsync方法。通知發送結束之後會分別觸發AnnounceOnlineCompleted/AnnounceOfflineCompleted事件。
前麵我們介紹了目標服務在上下線的時候如何發送通知,接下來我們站在客戶端的角度,談談如何監聽和接收通知。我們可以在客戶端開啟一個服務來服務監聽目標服務發送的上下線通知,而WCF已經為了定義了這麼一個服務,這就是具有如下定義的AnnouncementService。
1: [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,
2: ConcurrencyMode=ConcurrencyMode.Multiple)]
3: public class AnnouncementService :
4: IAnnouncementContractApril2005,
5: IAnnouncementContract11,
6: IAnnouncementContractCD1,...
7: {
8: //其他成員
9: public event EventHandler<AnnouncementEventArgs> OnlineAnnouncementReceived;
10: public event EventHandler<AnnouncementEventArgs> OfflineAnnouncementReceived;
11: }
12: public class AnnouncementEventArgs : EventArgs
13: {
14: //其他成員
15: public EndpointDiscoveryMetadata EndpointDiscoveryMetadata { get; }
16: public DiscoveryMessageSequence MessageSequence { get; }
17: }
對於這個AnnouncementService服務類型,首先需要關注的是它實現的服務契約。從上麵的定義可以看到,AnnouncementService實現了三個契約IAnnouncementContractApril2005、IAnnouncementContract11和IAnnouncementContractCD1,。
其次,在AnnouncementService類型上應用了ServiceBehaviorAttribute特性,並將InstanceContextMode屬性設置成InstanceContextMode.Single,所以AnnouncementService會以模式被寄宿。同時ConcurrencyMode屬性被設置為ConcurrencyMode.Multiple,所以該服務支持並發。
,因為它需要這樣的終結點進行通知的監聽與接收。當服務上/下線通知被接收之後,事件OnlineAnnouncementReceived/OfflineAnnouncementReceived分別被觸發。通過類型為AnnouncementEventArgs的事件參數,你可以獲得封裝目標服務元數據的EndpointDiscoveryMetadata對象和代表消息序列的DiscoveryMessageSequence對象。
實際上我們所說的對目標服務上/下線通知的監聽與接收就是在客戶端基於寄宿這麼一個AnnouncementService服務,並通過注冊這兩個事件獲得通知。我們將在《實例篇》中為你演示一個具體的例子。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 14:34:48