65.9K
CodeProject 正在变化。 阅读更多。
Home

WCF 4.0 Discovery

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (16投票s)

2012 年 10 月 3 日

CPOL

7分钟阅读

viewsIcon

56368

downloadIcon

4396

本文介绍 WCF 4.0 的发现系统。

目录

引言 

某些业务解决方案需要不同类型的启用服务的设备,这些设备会不断连接并发送到网络。也就是说,这些服务的运行时位置是动态的且不断变化的。为了连接到这些服务,客户端需要动态确定这些服务终结点的运行时位置。Windows Communication Foundation (WCF) 支持通过WS-Discovery 协议来启用这些服务在运行时可被发现。在本文中,我将介绍 WCF 4.0 的发现系统。 

底层技术

发现依赖于用户数据报协议 (UDP)UDP 是一种无连接协议,客户端和服务器之间不需要直接连接。客户端使用UDP 来广播对支持特定契约类型的任何终结点的查找请求。支持此契约的发现终结点将收到请求。发现终结点的实现会向客户端响应服务终结点地址。一旦客户端确定了服务,它就会调用服务来设置调用。

简单的服务发现

WCF 服务可以通过组播消息或发现代理服务器来广播其可用性。客户端应用程序可以搜索网络或发现代理服务器来发现满足一组标准的服务。服务发现有两种模式:临时发现托管发现。在临时发现中,客户端可以发现本地子网上的服务;在托管发现中,客户端可以通过发现代理在更大的托管网络上发现服务。

启用服务发现的最简单方法是通过临时模式。要为服务配置发现,我们需要添加标准的udpDiscoveryEndpoint 终结点,并且还需要在服务上启用serviceDiscovery 行为。一旦udpDiscoveryEndpoint 处于活动状态,客户端就可以通过 UDP 发现它并绑定到该终结点。如果服务公开了metadataExchange 终结点,客户端也可以发现它并动态绑定到该终结点。

服务如何设置?  

 <system.serviceModel>
    <services>
      <service name="Rashim.RND.AdhocDiscovery.Services.MessageService">
        <host>
          <baseAddresses>
            <add baseAddress="https://:8732/MessageService"/>
          </baseAddresses>
        </host>
        <endpoint address="/TEST" binding="wsHttpBinding" contract="Rashim.RND.AdhocDiscovery.Services.IMessageService"/>
        <endpoint kind="mexEndpoint"/>
        <endpoint kind="udpDiscoveryEndpoint"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata/>
          <serviceDiscovery/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior>
          <endpointDiscovery>
            <scopes>
              <add scope="ldap:///ou=people,o=rashim"/>
            </scopes>
          </endpointDiscovery>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>  

在服务部分,我们看到MessageService 有一个基地址并公开了三个终结点。mexEndpoint 用于metadataexchange,而udpDiscoveryEndpoint 用于UDP 发现。定义服务的行为有几个,使用了默认行为。在终结点行为中,我们添加了一个名为endpointDiscovery 的行为,并在终结点发现中添加了范围(scope)。范围在修改发现和添加时尚的行为方面非常有用。范围是一个对调用者和服务有意义的 URI。服务需要将一个或多个范围与其要发布的用于发现的每个终结点关联起来。然后,客户端可以根据范围发现服务。

客户端如何设置?  

var discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
            var findCriteria = FindCriteria.CreateMetadataExchangeEndpointCriteria(typeof(IMessageService));
            findCriteria.MaxResults = 1;
            Debug.Assert(findCriteria.Scopes != null, "findCriteria.Scopes != null");
            findCriteria.Scopes.Add(new Uri("ldap:///ou=people,o=rashim"));
            var findResponse = discoveryClient.Find(findCriteria);

            if (findResponse != null)
            {
                if(findResponse.Endpoints!=null)
                {
                    if (findResponse.Endpoints.Count > 0)
                    {
                        var endpoints = MetadataResolver.Resolve(typeof (IMessageService),
                                                                findResponse.Endpoints[0].Address);
                        var factory = new ChannelFactory<imessageservice>(endpoints[0].Binding, endpoints[0].Address);
                        var channel = factory.CreateChannel();

                        Console.WriteLine("Say something and press enter");
                        var input = Console.ReadLine();
                        while (input != null && input.ToString(CultureInfo.InvariantCulture).ToLower()!="exit")
                        {
                            Console.WriteLine(channel.GetMessage(input));
                            input = Console.ReadLine();
                        }
                          ((IChannel)channel).Close();                    
                    }
                }
            }

在这里,DiscoveryClient 允许我们发现可用的服务。FindCriteria 用于查找指定范围内的IMessageService 终结点。一旦客户端检索到已发现的终结点集合,它就可以使用其中一个来实际调用目标服务。所有这些都可以通过下面的配置文件来定义。  

<system.serviceModel>
    <client>
      <endpoint contract="Rashim.RND.AdhocDiscovery.Services.IMessageService"
           kind="dynamicEndpoint"
             endpointConfiguration="myDynamicEndpointConfig" name="discovery">

      </endpoint>
    </client>
    <standardEndpoints>
      <dynamicEndpoint>
        <standardEndpoint name="myDynamicEndpointConfig">
          <discoveryClientSettings>
            <endpoint kind="udpDiscoveryEndpoint" name="udpDiscoveryEndpoint"/>
            <findCriteria maxResults="1">
              <scopes>
                <add scope="ldap:///ou=people,o=rashim"/>
              </scopes>
            </findCriteria>
          </discoveryClientSettings>

        </standardEndpoint>
      </dynamicEndpoint>
    </standardEndpoints>
  </system.serviceModel>

服务公告  

WCF 发现功能支持服务将其状态广播给所有客户端并提供其地址的机制。也就是说,每个公告包含终结点地址、其范围和其契约。服务主机在线和离线时广播 Hello 和 Bye 公告。但是,如果主机被异常终止,则不会发送“bye”公告。客户端可以监听此类公告消息,然后执行必要的操作。这种公告服务减少了探测/组播消息的数量。

服务如何设置?  

<system.serviceModel>   
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDiscovery>
            <announcementEndpoints>
              <endpoint kind="udpAnnouncementEndpoint"/>
            </announcementEndpoints>
          </serviceDiscovery>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior>
          <endpointDiscovery enabled="true"/>          
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <services>
      <service name="Rashim.Discovery.Announcement.Server.MessageService">
        <host>
          <baseAddresses>
            <add baseAddress="https://:8085/MessageService"/>
          </baseAddresses>
        </host>
        <endpoint kind="mexEndpoint" />
        <endpoint kind="udpDiscoveryEndpoint"/>
        <!-- Application Endpoint -->
        <endpoint address="/test"
                  binding="basicHttpBinding" contract="Rashim.Discovery.Announcement.Common.IMessageServices"/>
        
      </service>
    </services>
  </system.serviceModel> 

我们通过使用serviceDiscovery 行为来配置带有公告终结点的服务。serviceDiscovery 行为允许您定义由服务公开的公告终结点集合。在大多数情况下,您可以使用标准的udpAnnouncementEndpoint。在配置文件中,我们看到announcementEndpoints 添加了一个udpAnnouncementEndpoint,它将在上线和离线时广播消息。我们还需要注意endpointDiscovery。它应该设置为 true,以便使特定终结点自动可发现。

客户端如何设置? 

var announcementService = new AnnouncementService();

            announcementService.OnlineAnnouncementReceived += (sender, e) =>
            {
                var mexContractDescrition = ContractDescription.GetContract(typeof(IMetadataExchange));
                var mexQualifiedName = new XmlQualifiedName(mexContractDescrition.Name, mexContractDescrition.Namespace);

                e.EndpointDiscoveryMetadata.ContractTypeNames.ToList().ForEach((name) =>
                                        {
                                            if (mexQualifiedName != name) return;
                                            
                                            var endpoints = MetadataResolver.Resolve(typeof(IMessageServices), e.EndpointDiscoveryMetadata.Address);
                                            
                                            if (endpoints.Count <= 0) return;
                                            
                                            var factory = new ChannelFactory<IMessageServices>(endpoints[0].Binding, endpoints[0].Address);
                                            var channel = factory.CreateChannel();
                                            
                                            Console.WriteLine("\nService is online now..");
                                            var replyMessage = channel.GetMessage("Server is responding");
                                            Console.WriteLine(replyMessage);
                                            ((ICommunicationObject)channel).Close();
                                        });
            };

            announcementService.OfflineAnnouncementReceived += (sender, e) =>
            {                
                if (e.EndpointDiscoveryMetadata.ContractTypeNames.FirstOrDefault(contract => contract.Name == typeof(IMessageServices).Name) == null) return;

                Console.WriteLine("\nService says 'Good Bye'");
            };

            using (var announecementServiceHost = new ServiceHost(announcementService))
            {
                announecementServiceHost.AddServiceEndpoint(new UdpAnnouncementEndpoint());
                announecementServiceHost.Open();
                Console.WriteLine("Please enter to exit\n\n");
                Console.ReadLine();                             
            }

客户端托管一个AnnouncementService 来监听公告,以响应 Hello 和 Bye 消息。AnnouncementService 提供了两个事件处理程序:OnlineAnnouncementReceivedOfflineAnnouncementReceived。客户端应用程序只需使用 ServiceHost 托管 AnnouncementService 的一个实例,并订阅 OnlineAnnouncementReceivedOfflineAnnouncementReceived 事件。请参考代码。 

托管发现 

当我们想在本地网络边界之外使用 WS-Discovery 时,就需要进行托管发现。托管发现允许我们定位服务,无论它们在哪里,只要它们已注册到发现代理。发现代理是一项独立服务,包含服务存储库。客户端可以查询发现代理来查找代理已知的可发现服务。代理如何填充服务则取决于实现者。 

System.ServiceModel.Discovery 命名空间包含一个基类,可以帮助我们构建发现代理。要实现我们的代理,我们将不得不重写 System.ServiceModel.Discovery.DiscoveryProxy 类提供的多个方法。

发现代理如何设置?

 var baseAddress =
                new Uri("https:///demo");

            using (var host = new System.ServiceModel.ServiceHost(new DiscoveryProxyService(), baseAddress))
            {               
                host.AddServiceEndpoint(new DiscoveryEndpoint(new WSHttpBinding(), new EndpointAddress(host.BaseAddresses[0]+"/probe")){IsSystemEndpoint = false});
                host.AddServiceEndpoint(new AnnouncementEndpoint(new WSHttpBinding(), new EndpointAddress(host.BaseAddresses[0] + "/announcement")) { IsSystemEndpoint = false });
                host.Description.Endpoints.ToList().ForEach((item)=> Console.WriteLine(item.ListenUri));
              
                host.Open();

                Console.WriteLine("Press enter to exit.");
                Console.ReadLine();
            } 

在上面的代码部分,主机添加了两个服务终结点,一个是DiscoveryEndpoint,指定客户端应在何处监听发现消息;另一个是AnnouncementEndpoint,指定服务应在何处发送公告消息。我在演示项目中使用的发现代理类是从这里获取的。

服务如何设置?   

<system.serviceModel>
    <services>
      <service name="Rashim.RND.ManagedDiscovery.DiscoverableService.StringService">
        <host>
          <baseAddresses>
            <add baseAddress="https:///StringService"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>      
      <endpointBehaviors>
        <behavior>
          <endpointDiscovery>
            <scopes>
              <add scope="ldap:///ou=people,o=rashim"/>
            </scopes>
          </endpointDiscovery>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>

在配置文件中,有一个基地址和一个范围已定义。该文件的详细信息已在上面的简单服务发现部分进行了讨论。 

 var announcementAddress =
                new Uri("https:///demo/announcement");
            var host = new System.ServiceModel.ServiceHost(typeof (StringService));

            try
            {
                host.AddDefaultEndpoints();
                var announcementEndpoint = new AnnouncementEndpoint(new WSHttpBinding(),
                                                                    new EndpointAddress(announcementAddress));
                var serviceDiscoveryBehavior = new ServiceDiscoveryBehavior();
                serviceDiscoveryBehavior.AnnouncementEndpoints.Add(announcementEndpoint);
                host.Description.Behaviors.Add(serviceDiscoveryBehavior);
                host.Open();

                host.Description.Endpoints.ToList().ForEach((endpoint) => Console.WriteLine(endpoint.ListenUri));
                Console.WriteLine("Please enter to exit");
                Console.ReadLine();
                host.Close();
            }
            catch (Exception oEx)
            {
                Console.WriteLine(oEx.ToString());
            }

在上面的代码中,主机添加了ServiceDiscoveryBehavior,它控制服务终结点的可发现性,而ServiceDiscoveryBehavior 添加了AnnouncementEndpoints,并带有DiscoveryProxy 公告的暴露地址。

客户端如何设置? 

var probeAddress = new Uri("https:///demo/probe");
            var discoveryEndpoint = new DiscoveryEndpoint(new WSHttpBinding(), new EndpointAddress(probeAddress));
            var discoveryClient = new DiscoveryClient(discoveryEndpoint);
            var findCriteria = new FindCriteria(typeof (IStringService));

            findCriteria.Scopes.Add(new Uri("ldap:///ou=people,o=rashim"));

            var findResponse = discoveryClient.Find(findCriteria);

            if (findResponse != null)
            {
                if (findResponse.Endpoints != null)
                {
                    if (findResponse.Endpoints.Count > 0)
                    {
                        var factory = new ChannelFactory<IStringService>(new BasicHttpBinding(),
                                                                         findResponse.Endpoints[0].Address);
                        var channel = factory.CreateChannel();
                        Console.WriteLine(channel.ToUpper("Rashim"));
                    }
                }
            }
            else
            {
                Console.WriteLine("Could not find the Services");
            } 

客户端使用DiscoveryProxy 提供的探测地址来搜索服务,也使用范围来缩小搜索结果。一旦客户端找到了指定的服务终结点,它就会调用。

使用解决方案 

有三个解决方案

  • Rashim.RND.AdhocDiscovery 
  • Rashim.Discovery.Announcement.Solution
  • Rashim.RND.ManagedDiscovery

Rashim.RND.AdhocDiscovery 是一个使用简单服务发现进行临时发现的示例应用程序。要查看演示的输出,您需要先运行Rashim.RND.AdhocDiscovery.ServiceHost,然后再运行Rashim.RND.AdhocDiscovery.Client

Rashim.Discovery.Announcement.Solution 也是一个使用公告服务进行临时发现的演示应用程序。对于此解决方案,您需要运行Rashim.Discovery.Announcement.Client,然后再运行Rashim.Discovery.Announcement.Server。请注意,如果您想退出服务器或客户端,只需按 Enter 键。如果服务器发生任何异常关闭/退出,客户端可能不会收到有关服务器到达和退出的通知。

Rashim.RND.ManagedDiscovery 解决方案是另一个使用发现代理进行托管发现的演示应用程序。要查看输出,您需要先运行Rashim.RND.ManagedDiscovery.DiscoveryProxy,然后再运行Rashim.RND.ManagedDiscovery.ServiceHost,最后还要运行Rashim.RND.ManagedDiscovery.Client。请注意,您需要保持项目运行的顺序

参考文献 

http://msdn.microsoft.com/en-us/library/dd456782.aspx 

http://msdn.microsoft.com/en-us/library/ee354381.aspx  

http://msdn.microsoft.com/en-us/magazine/ee335779.aspx 

http://geekswithblogs.net/shaunxu/archive/2012/07/06/service-discovery-in-wcf-4.0-ndash-part-1.aspx 

http://msdn.microsoft.com/en-us/library/dd456787.aspx 

就是这样 

目前就到这里,希望本文能帮助您开始使用 WCF 4.0 的发现系统。

© . All rights reserved.