[WCF-Discovery]让服务自动发送上/下线通知[原理篇]
到目前为止,我们所介绍的都是基于客户端驱动的服务发现模式,也就是说客户端主动发出请求以探测和解析可用的目标服务。在介绍WS-Discovery的时候,我们还谈到另外一种服务驱动的模式,即服务在上线和下线的时候主动对外发出Hello/Bye通知。服务上下线通知机制依赖另外一个AnnouncementEndpoint标准终结点。
目录
AnnouncementEnpoint
UdpAnnouncementEnpoint
上下线通知的发送
上下线通知的接收
一、AnnouncementEndpoint
在采用服务端驱动的情况下,目标服务通过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 = "http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01")]
2: internal interface IAnnouncementContract11
3: {
4: //Hello
5: [OperationContract(IsOneWay = true, Action = "http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Hello")]
6: void HelloOperation(HelloMessage11 message);
7: [OperationContract(IsOneWay = true, Action = "http://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 = "http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Bye")]
13: void ByeOperation(ByeMessage11 message);
14: [OperationContract(IsOneWay = true, Action = "http://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消息交换模型中的Hello/Bye部分,所以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>
二、UdpAnnouncementEndpoint
由于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实现上/下线通知的发送,所以我们在调用构造函数来创建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,它们正是AnnouncementEndpoint针对不同WS-Discovery版本所采用的契约。
其次,在AnnouncementService类型上应用了ServiceBehaviorAttribute特性,并将InstanceContextMode属性设置成InstanceContextMode.Single,所以AnnouncementService会以单例模式被寄宿。同时ConcurrencyMode属性被设置为ConcurrencyMode.Multiple,所以该服务支持并发。
AnnouncementService服务被寄宿的时候所添加的终结点必须是AnnouncementEndpoint终结点,因为它需要这样的终结点进行通知的监听与接收。当服务上/下线通知被接收之后,事件OnlineAnnouncementReceived/OfflineAnnouncementReceived分别被触发。通过类型为AnnouncementEventArgs的事件参数,你可以获得封装目标服务元数据的EndpointDiscoveryMetadata对象和代表消息序列的DiscoveryMessageSequence对象。
实际上我们所说的对目标服务上/下线通知的监听与接收就是在客户端基于寄宿这么一个AnnouncementService服务,并通过注册OnlineAnnouncementReceived/OfflineAnnouncementReceived这两个事件获得通知。我们将在《实例篇》中为你演示一个具体的例子。