WCF Discovery 概述

Discovery API 提供了统一的编程模型来使用 WS-Discovery 协议动态发布和发现 Web 服务。通过这些 API,服务可以发布自身,客户端可以查找已发布的服务。服务一旦可供检测,即可发送公告消息,并侦听和响应发现请求。可检测到的服务可以发送 Hello 消息和 Bye 消息,前者用于公告服务将到达网络,后者用于公告服务将离开网络。若要查找服务,客户端将在网络上发送包含特定条件(如服务协定类型、关键字和范围)的 Probe 请求。服务接收到此 Probe 请求,并确定它们是否匹配该条件。如果某一服务匹配该条件,该服务会做出响应,向客户端回发一条 ProbeMatch 消息,该消息包含与该服务联系所需的信息。客户端还可以发送 Resolve 请求,以便查找可能已更改终结点地址的服务。匹配的服务会向客户端回发一条 ResolveMatch 消息,以此来响应 Resolve 请求。

临时模式和托管模式

Discovery API 支持两种不同模式:托管模式和临时模式。托管模式中存在一台称为发现代理的中央服务器,用于维护有关可用服务的信息。可以采用多种方式使用服务相关信息填充发现代理。例如,服务可以在启动时向发现代理发送公告消息,或者代理也可以从数据库或配置文件中读取数据以确定可用服务。发现代理的填充方式完全由开发人员决定。客户端使用发现代理检索有关可用服务的信息。当客户端搜索服务时,它会向发现代理发送一条 Probe 消息,然后由代理确定已知的任何服务是否与客户端搜索的服务匹配。如果存在匹配服务,发现代理会向客户端回发 ProbeMatch 响应。然后,客户端可以使用代理返回的服务信息,直接与该服务联系。托管模式所依据的关键原理是:以单播方式向一个机构(即发现代理)发送发现请求。通过 .NET Framework 包含的关键组件,您可以生成自己的代理。客户端和服务可以采用多种方法来定位代理:

  • 代理可以响应临时消息。

  • 代理可以在启动时发送公告消息。

  • 可以编写客户端和服务来查找特定的已知终结点。

临时模式中没有任何中央服务器。服务公告和客户端请求等所有发现消息都以多播方式发送。默认情况下,.NET Framework 包含对基于 UDP 协议的临时发现的支持。例如,如果将服务配置为在启动时发出 Hello 公告,则该服务采用 UDP 协议通过已知多播地址来发出此公告。客户端必须主动侦听这些公告,并对这些公告进行相应处理。当客户端针对某一服务发送 Probe 消息时,会通过采用多播协议的网络进行发送。接收到请求的各服务确定自身是否与 Probe 消息中的条件匹配,如果服务与 Probe 消息中指定的条件匹配,服务将直接使用 ProbeMatch 消息响应客户端。

使用 WCF Discovery 的好处

由于 WCF Discovery 是采用 WS-Discovery 协议实现的,因此它可以与其他客户端、服务以及实现 WS-Discovery 的代理进行互操作。WCF Discovery 建立在现有 WCF API 的基础上,从而可以向现有服务和客户端方便地添加 Discovery 功能。通过应用程序配置设置,可以轻松地添加服务发现功能。此外,WCF Discovery 还支持在其他传输(如对等网络、命名覆盖和 HTTP)中使用发现协议。WCF Discovery 支持采用发现代理的托管运行模式。由于消息直接发送到发现代理,而不是将多播消息发送到整个网络,因此这样可以减少网络流量。使用 Web 服务时,WCF Discovery 还可以提高灵活性。例如,您可以更改服务地址,而不必重新配置客户端或服务。当客户端必须访问该服务时,它通过 Find 请求发出 Probe 消息,并希望该服务使用其当前地址进行响应。WCF Discovery 允许客户端基于不同条件搜索服务,这些条件包括协定类型、绑定元素、命名空间、范围以及关键字或版本号。WCF Discovery 支持运行时和设计时发现功能。可以将发现功能添加到应用程序中,这一特点可用于启用其他方案,如容错和自动配置。

服务发布

若要使服务可供检测,必须向服务主机添加 ServiceDiscoveryBehavior,并且必须添加发现终结点,以便指定侦听发现消息的位置。下面的代码示例演示如何修改自承载服务,以使该服务可供检测。

Uri baseAddress = new Uri(string.Format("http://{0}:8000/discovery/scenarios/calculatorservice/{1}/",
        System.Net.Dns.GetHostName(), Guid.NewGuid().ToString()));

// Create a ServiceHost for the CalculatorService type.
using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService), baseAddress))
{
    // add calculator endpoint
    serviceHost.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), string.Empty);

    // ** DISCOVERY ** //
    // make the service discoverable by adding the discovery behavior
    serviceHost.Description.Behaviors.Add(new ServiceDiscoveryBehavior());

    // ** DISCOVERY ** //
    // add the discovery endpoint that specifies where to publish the services
    serviceHost.AddServiceEndpoint(new UdpDiscoveryEndpoint());
    
    // Open the ServiceHost to create listeners and start listening for messages.
    serviceHost.Open();

    // The service can now be accessed.
    Console.WriteLine("Press <ENTER> to terminate service.");
    Console.ReadLine();
}

必须在服务说明中添加 ServiceDiscoveryBehavior 实例,以使服务可供检测。必须向服务主机添加 DiscoveryEndpoint 实例,以将发现请求的侦听位置告知服务。在本示例中,添加了 UdpDiscoveryEndpoint(派生自 DiscoveryEndpoint),用于指定服务应通过 UDP 多播传输侦听发现请求。由于所有消息均以多播方式发送,因此,UdpDiscoveryEndpoint 用于临时发现。

公告

默认情况下,发布服务时不会发出公告消息。必须对服务进行配置才能发出公告消息。这就为服务编写器提供了额外的灵活性,因为它们可以分别通告服务和侦听发现消息。服务公告还可用作向发现代理或其他服务注册表注册服务的机制。下面的代码演示如何将服务配置为通过 UDP 绑定发送公告消息。

Uri baseAddress = new Uri(string.Format("http://{0}:8000/discovery/scenarios/calculatorservice/{1}/",
        System.Net.Dns.GetHostName(), Guid.NewGuid().ToString()));

// Create a ServiceHost for the CalculatorService type.
using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService), baseAddress))
{
    // add calculator endpoint
    serviceHost.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), string.Empty);

    // ** DISCOVERY ** //
    // make the service discoverable by adding the discovery behavior
    ServiceDiscoveryBehavior discoveryBehavior = new ServiceDiscoveryBehavior();
    serviceHost.Description.Behaviors.Add(new ServiceDiscoveryBehavior());

    // send announcements on UDP multicast transport
    discoveryBehavior.AnnouncementEndpoints.Add(
      new UdpAnnouncementEndpoint());
    
    // ** DISCOVERY ** //
    // add the discovery endpoint that specifies where to publish the services
    serviceHost.Description.Endpoints.Add(new UdpDiscoveryEndpoint());
    
    // Open the ServiceHost to create listeners and start listening for messages.
    serviceHost.Open();

    // The service can now be accessed.
    Console.WriteLine("Press <ENTER> to terminate service.");
    Console.ReadLine();
}

服务发现

客户端应用程序可以使用 DiscoveryClient 类查找服务。开发人员可创建 DiscoveryClient 类的实例,用于传入指定 ProbeResolve 消息的发送位置的发现终结点。然后,客户端调用 Find,用于指定 FindCriteria 实例中的搜索条件。如果找到匹配的服务,则 Find 返回 EndpointDiscoveryMetadata 的集合。下面的代码演示如何调用 Find 方法并连接到已发现的服务。

class Client
{
    static EndpointAddress serviceAddress;

    static void Main()
    {
        if (FindService()) InvokeService();
    }

    // ** DISCOVERY ** //
    static bool FindService()
    {
        Console.WriteLine("\nFinding Calculator Service ..");
        DiscoveryClient discoveryClient = 
            new DiscoveryClient(new UdpDiscoveryEndpoint());

        Collection<EndpointDiscoveryMetadata> calculatorServices = 
            discoveryClient.Find(new FindCriteria(typeof(ICalculator)));

        discoveryClient.Close();

        if (calculatorServices.Count == 0)
        {
            Console.WriteLine("\nNo services are found.");
            return false;
        }
        else
        {
            serviceAddress = calculatorServices[0].EndpointAddress;
            return true;
        }
    }

    static void InvokeService()
    {
        Console.WriteLine("\nInvoking Calculator Service at {0}\n", serviceAddress);

        // Create a client
        CalculatorClient client = new CalculatorClient();
        client.Endpoint.Address = serviceAddress;
        client.Add(10,3);
}

发现和消息级别安全

使用消息级别安全时,必须在服务发现终结点上指定 EndpointIdentity,并在客户端发现终结点上指定匹配的 EndpointIdentity。有关消息级别安全的更多信息,请参见 WCF 中的消息安全