[WCF-Discovery] 客户端如何能够“探测”到可用的服务?
当应用了ServiceDiscoveryBehavior行为的服务通过标准终结点DiscoveryEndpoint被发布出来之后(《[WCF-Discovery]服务如何能被”发现”》),客户端就可以按照WS-Discovery中定义的方式对可用的目标方式进行探测和解析了。由于这个过程本质上就是一次普通的服务调用,具体来说是针对发布发现服务(非目标服务)的标准终结点DiscoveryEndpoint的调用,所以客户端也需要具有这么一个匹配的终结点。
目录:
一、DiscoveryClient
二、FindCriteria/FindResponse
三、ResolveCriteria/ResolveResponse
一、DiscoveryClient
客户端针对可用目标服务的探测与解析都是通过DiscoveryClient对象来实现的,下面的代码片断给出了DiscoveryClient的定义。我们可以直接通过一个DiscoveryEndpoint对象,或者是DiscoveryEndpoint的配置名称来创建DiscoveryClient对象。
1: public sealed class DiscoveryClient : ICommunicationObject, IDisposable,...
2: {
3: //事件
4: public event EventHandler<FindCompletedEventArgs> FindCompleted;
5: public event EventHandler<ResolveCompletedEventArgs> ResolveCompleted;
6:
7: //构造函数
8: public DiscoveryClient(DiscoveryEndpoint discoveryEndpoint);
9: public DiscoveryClient(string endpointConfigurationName);
10:
11: //Find
12: public FindResponse Find(FindCriteria criteria);
13: public void FindAsync(FindCriteria criteria);
14: public void FindAsync(FindCriteria criteria, object userState);
15:
16: //Resolve
17: public ResolveResponse Resolve(ResolveCriteria criteria);
18: public void ResolveAsync(ResolveCriteria criteria);
19: public void ResolveAsync(ResolveCriteria criteria, object userState);
20: }
DiscoveryClient定义了两套方法,一套是Find/FindAsync,另一套则是Resolve/ResolveAysnc。实际上Find相当于是WS-Discovery中的Probe,而Resolve自然就是WS-Discovery中的Resolve了。其中Find/Resolve采用同步的调用方式,而FindAsync/ ResolveAysnc则采用异步调用,异步调用完成的时候会触发事件FindCompleted/ResolveCompleted。
不论是用于可用服务探测的Find/FindAsync,还是用于目标服务解析的Resolve/ResolveAysnc,都需要指定相应的匹配条件。前者对应的匹配条件通过类型FindCriteria来表示,而后者匹配条件的类型则是ResolveCriteria。
同步方法Find/Resolve的返回类型分别为FindResponse和ResolveResponse。而对于异步调用,则可以通过注册的FindCompleted/ResolveCompleted事件参数中获取类型为FindResponse/ResolveResponse的返回值。这两个事件的参数类型分别为FindCompletedEventArgs和ResolveCompletedEventArgs,这个两个类型和它们的基类System.ComponentModel.AsyncCompletedEventArgs定义如下。
1: public class FindCompletedEventArgs : AsyncCompletedEventArgs
2: {
3: //其他成员
4: public FindResponse Result { get; }
5: }
6: public class ResolveCompletedEventArgs : AsyncCompletedEventArgs
7: {
8: //其他成员
9: public ResolveResponse Result { get; }
10: }
11: public class AsyncCompletedEventArgs : EventArgs
12: {
13: //其他成员
14: public bool Cancelled { get; }
15: public Exception Error { get; }
16: public object UserState { get; }
17: }
二、FindCriteria/ FindResponse
代表Probe请求的Find方法接受一个FindCriteria类的输入参数作为进行探测可用目标的匹配条件,该类型的主要的属性成员定义如下。其中ContractTypeNames代表探测的目标服务实现的契约类型列表,而Scopes和ScopeMatchBy则分别代表了用于探测目标的的范围和对范围进行匹配的方式。
1: public class FindCriteria
2: {
3: //其他成员
4: public static readonly Uri ScopeMatchByExact;
5: public static readonly Uri ScopeMatchByLdap;
6: public static readonly Uri ScopeMatchByNone;
7: public static readonly Uri ScopeMatchByPrefix;
8: public static readonly Uri ScopeMatchByUuid;
9:
10: public Collection<XmlQualifiedName> ContractTypeNames { get; }
11: public Collection<Uri> Scopes { get; }
12: public Uri ScopeMatchBy { get; set; }
13: }
目标的探测范围通过一个Uri的集合表示。客户端要通过范围进行目标服务的探测,前提是目标服务预先得与表示范围的Uri相关联。服务(实际上是指服务的某个终结点)的范围关联通过终结点行为EndpointDiscoveryBehavior来指定。如下面的代码片断所示,和FindCriteria一样,EndpointDiscoveryBehavior同样具有一个Uri集合类型的Scopes属性。
1: public class EndpointDiscoveryBehavior : IEndpointBehavior
2: {
3: //其他成员
4: public Collection<Uri> Scopes { get; }
5: }
在服务寄宿的时候,我们将表示服务范围的Uri列表定义在EndpointDiscoveryBehavior终结点行为中,并通过将此行为应用在寄宿服务相应的终结点上,从而实现了服务(终结点)与范围的关联。在下面的配置中,我定义了一个名为scopeMatch的终结点行为将表示服务范围的两个Uri应用到了服务的终结点上。
1: <configuration>
2: <system.serviceModel>
3: <services>
4: <service ...>
5: <endpoint behaviorConfiguration="scopeMatch" .../>
6: ...
7: </service>
8: </services>
9: <behaviors>
10: <endpointBehaviors>
11: <behavior name="scopeMatch">
12: <endpointDiscovery>
13: <scopes>
14: <add scope="http://www.example.com/calculator"/>
15: <add scope="ldap:///ou=engineering,o=examplecom,c=us"/>
16: </scopes>
17: </endpointDiscovery>
18: </behavior>
19: </endpointBehaviors>
20: </system.serviceModel>
21: </configuration>
当服务接收到带有服务范围列表作为匹配条件的探测请求时,在进行匹配判断的时候就验证相应终结点关联的范围是否指定的范围之类。至于具体采用的范围匹配的逻辑,则取决于FindCriteria的ScopeMatchBy属性。
FindCriteria的ScopeMatchBy属性类型依然是Uri。WCF预选定义了5个Uri代表相应的进行范围匹配的5种算法,它们对应着定义在FindCriteria的5个静态只读属性:ScopeMatchByExact、ScopeMatchByLdap、ScopeMatchByNone、ScopeMatchByPrefix和ScopeMatchByUuid。
1: public class FindCriteria
2: {
3: //其他成员
4: public static readonly Uri ScopeMatchByExact;
5: public static readonly Uri ScopeMatchByLdap;
6: public static readonly Uri ScopeMatchByNone;
7: public static readonly Uri ScopeMatchByPrefix;
8: public static readonly Uri ScopeMatchByUuid;
9:
10: public Uri ScopeMatchBy { get; set; }
11: }
下面的列表列出了这5个静态字段分别代表了何种服务范围匹配算法,以及各自具有怎样的Uri值。实际上这些代表服务范围匹配算法的Uri也是定义在WS-Discovery规范之中,但是为了避免为实现对不同版本的WS-Discovery的支持而采用不同的Uri,WCF在这里并没有真正地采用定义在相应版本的WS-Discovery中的Uri,而是定义了自己的常量。在对Probe消息进行序列化的时候,会转换成相应WS-Discovery支持的Uri。
- ScopeMatchByExact:对Uri进行精确匹配,Uri为http://schemas.microsoft.com/ws/2008/06/discovery/strcmp0;
- ScopeMatchByPrefix:将指定的Uri作为服务范围的前缀进行匹配,Uri为http://schemas.microsoft.com/ws/2008/06/discovery/rfc;
- ScopeMatchByLdap:按使用LDAP URL的段来匹配范围,Uri为http://schemas.microsoft.com/ws/2008/06/discovery/ldap;
- ScopeMatchByUuid:通过使用UUID字符串来完全匹配范围,Uri为http://schemas.microsoft.com/ws/2008/06/discovery/uuid;
- ScopeMatchByNone:仅匹配那些未指定范围的服务,Uri为http://schemas.microsoft.com/ws/2008/06/discovery/none。
如果采用ScopeMatchByExact,进行精确匹配是区分大小写的。而基于前缀匹配的ScopeMatchByPrefix,实际上按以“/”分隔的段进行匹配。搜索 http://contoso/building1 与范围为 http://contoso/building/floor1 的服务相匹配。请注意,该搜索与http://contoso/building100 不匹配,因为最后两个段不匹配。ScopeMatchBy的值必须指定为上述的5种Uri之一,其他各式的Uri是无效的。如果未指定范围匹配规则,则使用ScopeMatchByPrefix。
按照WS-Discovery定义的消息交换模式来看,客户端针对Find/FindAsync方法调用实际上就是发送Probe请求。符合匹配条件的目标服务会回复以PM消息,该消息中会包含服务相关的元数据信息。最终这些PM消息中的内容会被提取出来,被封装成FindResponse对象并最为Find方法的返回值(或者事件参数FindCompletedEventArgs的Result属性)。接下来,我们将重点介绍FindResponse这个类型。
1: public class FindResponse
2: {
3: //其他成员
4: public DiscoveryMessageSequence GetMessageSequence(EndpointDiscoveryMetadata endpointDiscoveryMetadata);
5: public Collection<EndpointDiscoveryMetadata> Endpoints { get; }
6: }
如上面的代码所示,FindResponse具有一个核心的只读属性Endpoints,其类型是一个元素类型为EndpointDiscoveryMetadata的集合。顾名思义,EndpointDiscoveryMetadata就是代表探测到的服务制定匹配条件的服务的终结点的元数据。EndpointDiscoveryMetadata定义如下,通过相应的属性可以得到代表目标服务终结点的地址、契约类型列表、监听地址、服务范围和扩展和版本相关信息。
1: public class EndpointDiscoveryMetadata
2: {
3: //其他成员
4: public EndpointAddress Address { get; set; }
5: public Collection<XmlQualifiedName> ContractTypeNames { get; }
6: public Collection<XElement> Extensions { get; }
7: public Collection<Uri> ListenUris { get; }
8: public Collection<Uri> Scopes { get; }
9: public int Version { get; set; }
10: }
FindResponse除了具有一个表述目标服务终结点元数据的Endpoints属性之外,还具有一个GetMessageSequence方法,该方法以EndpointDiscoveryMetadata对象作为输入,返回一个System.ServiceModel.Discovery.DiscoveryMessageSequence对象。DiscoveryMessageSequence被称为消息序列,涉及到定义在WS-Discovery中的一个重要的概念应用序列(Application Sequence/AppSequence)。简单起见,我们可以这样来理解:在采用广播模式的服务发现截至无法确保消息的有序接收,即不能确保消息按照它被发送的顺序被接收(先发先至),所以需要相应的序号封装在一个被称为AppSequence的报头中被发送。DiscoveryMessageSequence类型定义如下,它的三个只读属性分别对应着AppSequence报头的相应的属性(Attribute),具体的含义请参考WS-Discovery规范。
1: public class DiscoveryMessageSequence : ...
2: {
3: //其他成员
4: public long InstanceId {get; }
5: public long MessageNumber {get; }
6: public Uri SequenceId {get; }
7: }
三、ResolveCriteria/ResolveResponse
上面我们介绍了用于进行可用服务探测的Find/FindAsync操作的输入和输出,接下俩我们按照相同的方式来分析用于进行服务解析的Resolve/ResolveAsync操作的输入和输出。首先来介绍一下用于封装匹配条件的ResolveCriteria类型,下面给出了它核心的属性定义。
1: public class ResolveCriteria
2: {
3: //其他成员
4: public EndpointAddress Address { get; set; }
5: public Collection<XElement> Extensions { get; }
6: public TimeSpan Duration { get; set; }
7: }
其中Address属性表示被解析的服务的终结点地址,而Exntesions代表以XElement集合表示的扩展信息。Duration属性表示Resolve操作执行的超时时限,即要求对于Resolve请求,在规定的时限内必须得到回复。如果没有进行显式设置,Durarion属性采用默认值20秒。
而作为Resolve/ResolveAsync输出的ResolveResponse类型定义很简单。它具有两个核心的只读属性,代表被解析后的服务终结点元数据的EndpointDiscoveryMetadata属性和代表消息序列的MessageSequence属性。
1: public class ResolveResponse
2: {
3: //其他成员
4: public EndpointDiscoveryMetadata EndpointDiscoveryMetadata { get;}
5: public DiscoveryMessageSequence MessageSequence { get;}
6: }