基础内容

通过发现查找新 WCF

Juval Lowy

下载代码示例

Microsoft .NET Framework 3.5 对 Windows Communication Foundation (WCF) 的全部可能调用均具有两个限制。第一个限制是,分配给服务的端口或管道必须可用。因此,应用程序开发人员或管理员必须试想或者提供某种方法,以保留这些端口或管道。第二个限制是,客户端必须提前知道服务端点地址,包括端口号和服务器或管道名称。

最理想的情况是,服务能够使用任何可用地址。反过来,客户端就需要在运行时发现此地址。事实上,存在一种基于行业标准的解决方案,用于规定此发现的定位方式。本专栏的主题就是该解决方案(简称为“发现”)及其支持机制。同时,我还会介绍几个有用的工具和帮助程序类。这些内容的源代码,可在 code.msdn 中找到。

地址发现

发现依赖于用户数据报协议 (UDP)。与传输控制协议 (TCP) 不同,UDP 是无连接协议,在数据包发送者和接收者之间不需要建立直接连接。客户端使用 UDP 传播对任何支持指定约定类型的端点的发现请求。服务所支持的专门发现端点将接收这些请求。发现端点的实现将响应客户端,以提供支持指定约定的服务端点的地址。客户端发现服务后将对其进行调用,与常规 WCF 调用相同。有关该过程,在图 1 中进行了演示。

图 1 通过 UDP 的发现地址

image: Address Discovery over UDP

与元数据交换 (MEX) 端点非常类似,WCF 提供了类型为 UdpDiscoveryEndpoint 的标准发现端点:

public class DiscoveryEndpoint : ServiceEndpoint

{...}

public class UdpDiscoveryEndpoint : DiscoveryEndpoint

{...}

通过在服务支持的行为集合中添加 ServiceDiscoveryBehavior,可以使主机实现该端点。可以通过以下编程方式实现此目的:

ServiceHost host = new ServiceHost(...); 
host.AddServiceEndpoint(new UdpDiscoveryEndpoint());
ServiceDiscoveryBehavior discovery = new ServiceDiscoveryBehavior();
host.Description.Behaviors.Add(discovery);
host.Open();

图 2 显示如何使用服务配置文件添加发现端点和发现行为。

图 2 在配置文件中添加发现端点

<services>
   <service name = "MyService">
      <endpoint 
         kind = "udpDiscoveryEndpoint"
      />
      ...
   </service>
</services>
<behaviors>
   <serviceBehaviors>
      <behavior>
         <serviceDiscovery/>
      </behavior>
   </serviceBehaviors>
</behaviors>

动态地址

发现与服务主机定义其端点的准确度无关。但是,当客户端希望通过发现查找服务地址时,应该如何做?在这种情况下,服务可以基于任何可用端口或管道对其端点地址进行自由动态配置。 

为了使使用动态地址自动化,我编写了 DiscoveryHelper 静态帮助程序类,其中包含两个属性:AvailableIpcBaseAddress 和 AvailableTcpBaseAddress:

public static class DiscoveryHelper
{
   public static Uri AvailableIpcBaseAddress
   {get;}
   public static Uri AvailableTcpBaseAddress
   {get;}
}

实现 AvailableIpcBaseAddress 很简单,因为任何唯一命名的管道都可以实现,该属性使用新的全局唯一标识符 (GUID) 命名管道。对于实现 AvailableTcpBaseAddress,需要通过打开端口 0 查找可用 TCP 端口。

图 3 显示如何使用 AvailableTcpBaseAddress。

图 3 使用动态地址

Uri baseAddress = DiscoveryHelper.AvailableTcpBaseAddress;
ServiceHost host = new ServiceHost(typeof(MyService),baseAddress);
host.AddDefaultEndpoints();
host.Open();
<service name = "MyService">
   <endpoint 
      kind = "udpDiscoveryEndpoint"
   />
</service>
<serviceBehaviors>
   <behavior>
      <serviceDiscovery/>
   </behavior>
</serviceBehaviors>

如果您只想获得服务的动态基址,则仅凭图 3 中的代码还不能实现此目的,因为还需要在配置文件中或以编程方式添加发现。 可以使用 EnableDiscovery 主机扩展简化这些步骤,定义如下:

public static class DiscoveryHelper
{
   public static void EnableDiscovery(this ServiceHost host,bool enableMEX = true);
}

在使用 EnableDiscovery 时,不需要编程步骤或配置文件:

Uri baseAddress = DiscoveryHelper.AvailableTcpBaseAddress;
ServiceHost host = new ServiceHost(typeof(MyService),baseAddress);
host.EnableDiscovery();
host.Open();

如果主机尚未为服务定义端点,EnableDiscovery 将添加默认端点。此外,EnableDiscovery 默认向服务的基址上添加 MEX 端点。

客户端步骤

客户端使用 DiscoveryClient 类发现支持指定约定的所有服务的全部端点地址:

public sealed class DiscoveryClient : ICommunicationObject
{
    public DiscoveryClient();
    public DiscoveryClient(string endpointName);
    public DiscoveryClient(DiscoveryEndpoint discoveryEndpoint);
    public FindResponse Find(FindCriteria criteria);
   //More members
}

从逻辑上讲,DiscoveryClient 是发现端点的代理。与所有代理类似,客户端必须向代理的构造函数提供有关目标端点的信息。为此,客户端可以使用配置文件指定端点或以编程的方式提供标准 UDP 发现端点,因为不需要地址或绑定等更多详细信息。然后,客户端调用 Find 方法,并通过 FindCriteria 实例为其提供发现的约定类型:

public class FindCriteria

{

   public FindCriteria(Type contractType);
   //More members

}

Find 返回一个 FindResponse 实例,其中包含所有已发现端点的集合:

public class FindResponse

{
   public Collection<EndpointDiscoveryMetadata> Endpoints
   {get;}
   //More members
}

每个端点都由 EndpointDiscoveryMetadata 类表示:

public class EndpointDiscoveryMetadata
{
   public EndpointAddress Address
  {get;set;}
   //More members
}

EndpointDiscoveryMetadata 的主要属性是地址,该属性最终将包含发现的端点地址。图 4 显示了客户端如何结合使用这些类型以发现端点地址并调用服务。

图 4 发现和调用端点

DiscoveryClient discoveryClient = 
   new DiscoveryClient(new UdpDiscoveryEndpoint());
FindCriteria criteria = new FindCriteria(typeof(IMyContract));
FindResponse discovered = discoveryClient.Find(criteria);
discoveryClient.Close();
//Just grab the first found
EndpointAddress address = discovered.Endpoints[0].Address;
Binding binding = new NetTcpBinding();
IMyContract proxy = 
   ChannelFactory<IMyContract>.CreateChannel(binding,address);
proxy.MyMethod();
(proxy as ICommunicationObject).Close();

图 4 中有几个值得注意的问题。

客户端可能发现多个支持所需约定的端点,但它不具有逻辑分析功能以确定应该调用哪个端点。只调用返回的集合中的第一个端点。

发现仅用于地址发现。其中未提供有关使用哪个绑定以调用服务的信息。图 4 只是对 TCP 绑定的使用进行了硬编码。每当客户端需要发现服务地址时,不得不反复重复这些极小的步骤。

发现需要花费时间。默认情况下,Find 方法将等待 20 秒以获取响应 UDP 发现请求的服务。这样的延迟使得发现不适用于许多应用程序,特别是应用程序执行大量紧密调用时。您可以缩短该超时,但如果这么做,可能会无法发现部分或所有服务。DiscoveryClient 不提供异步发现,但异步发现不适用于需要调用服务后才可以继续执行的客户端。 

在本专栏的稍后部分提供了多个解决这些问题的方法。

作用域

使用发现可能会导致客户端与服务或其发现的服务之间的关系有些松散。这又出现另一系列问题 — 客户端如何可以知道发现了正确的端点?当发现多个兼容端点时,客户端应该调用哪一个?

毫无疑问,此时就需要某个机制来帮助客户端筛选发现结果。这正是作用域的作用所在。作用域只是与端点关联的有效 URL。服务可将一个作用域、或者甚至多个作用域与它的每个端点关联。作用域与响应发现请求的地址绑定在一起。这样,客户端可以基于找到的作用域筛选发现的地址,甚至还可以在开始时就只尝试查找相关的作用域。

作用域非常适用于自定义发现以及向应用程序添加复杂行为,尤其适用于编写框架或管理工具。作用域的典型用法,就是使客户端可以区分来自不同应用程序的不同形式的服务。但是,这种情况有些少见。我发现作用域很适合区分同一应用程序中的端点类型。

例如,假设对于某个给定约定,您具有多个实现。您在生产环境中使用操作模式,在测试或诊断环境中使用模拟模式。使用作用域,客户端可以挑选所需的正确实现,并且不同的客户端不会因为使用他人的服务而发生冲突。还可以使同一客户端根据调用上下文选择不同的端点。可对端点进行分析、调试、诊断、测试、检测等等。 

主机使用 EndpointDiscoveryBehavior 类为每个端点分配作用域。例如,要为所有端点都应用作用域,请使用以下默认端点行为:

<endpointBehaviors>
   <behavior>
      <endpointDiscovery>
         <scopes>
            <add scope = "net.tcp://MyApplication"/>
         </scopes>
      </endpointDiscovery>
   </behavior>
</endpointBehaviors>

根据服务类型,通过为每个端点显式分配行为,对它们分别应用作用域,如图 5 中所示。

图 5 显式行为分配

<service name = "MySimulator">
   <endpoint behaviorConfiguration = "SimulationScope"
      ...
   />
   ...
</service>
...
   <behavior name = "SimulationScope">
      <endpointDiscovery>
         <scopes>
            <add scope = "net.tcp://Simulation"/>
         </scopes>
      </endpointDiscovery>
   </behavior>

一个发现行为可列出多个作用域:

<endpointDiscovery>
   <scopes>
      <add scope = "net.tcp://MyScope1"/> 
      <add scope = "net.tcp://MyScope2"/>
   </scopes>
</endpointDiscovery>

如果端点具有多个关联的作用域,当客户端尝试基于作用域匹配发现该端点时,至少需要其中一个匹配的作用域,而不是所有。

存在两种客户端使用作用域的方法。第一种是将作用域添加到查找条件中:

public class FindCriteria
{
   public Collection<Uri> Scopes
  {get;}
   //More members
}

现在,Find 方法将仅返回兼容端点,其中也列出该作用域。如果客户端添加多个作用域,Find 将仅返回支持所有列出作用域的端点。请注意,端点可能支持未提供给 Find 的其他作用域。

第二种是用于检查在 FindResponse 中返回的作用域:

public class EndpointDiscoveryMetadata
{
   public Collection<Uri> Scopes
   {get;}
  //More members
}

这些作用域是端点支持的所有作用域,用于附加筛选。 

发现基数

每当使用发现时,客户端必须处理我所命名的“发现基数”,即发现的端点数及要调用哪一个(如果存在)。存在下列几种基数情况:

  • 未发现端点。在此情况下,客户端需要处理不存在服务的问题。这与其服务不可用的任何其他 WCF 客户端无异。
  • 只发现一个兼容端点。目前为止这是最常见的情况 — 客户端只需继续调用此服务即可。
  • 发现多个端点。此时,从理论上讲,客户端有两个选择。第一个选择是调用全部端点。这种情况适用于发布者引发有关订阅者的事件(相关内容稍后讨论),是一个有效的方案。第二个选择是调用一些(包括仅调用一个),而不是全部发现的端点。我发现这种方案豪无意义。每次尝试为客户端实现逻辑以解析要调用哪个端点时, 在系统上都将创建太多耦合。这否定了运行时发现的观点,即任何发现的端点都将被调用。如果可能发现不需要的端点,则使用发现是一个蹩脚的设计选择,并且您还需要向客户端提供静态地址。

如果客户端希望只发现一个端点(一个基数),则客户端应该指示 Find 在发现端点后立即返回。这样做会大大缩短发现延迟,从而使 Find 适用于大多数情况。

客户端可以使用 FindCriteria 的 MaxResults 属性配置基数:

public class FindCriteria
{
   public int MaxResults
   {get;set;}
   //More members
}
FindCriteria criteria = new FindCriteria(typeof(IMyContract));
criteria.MaxResults = 1;

对于一个基数的情况,可以使用我的 Discovery​Helper.DiscoverAddress<T> 帮助程序方法简化:

public static class DiscoveryHelper
{
   public static EndpointAddress DiscoverAddress<T>(Uri scope = null);
   //More members
}

使用 DiscoverAddress<T>,图 4 将简化为:

EndpointAddress address = DiscoveryHelper.DiscoverAddress<IMyContract>();
Binding binding = new NetTcpBinding();
IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel(binding,address);
proxy.MyMethod();
(proxy as ICommunicationObject).Close();

简化发现

目前为止,客户端必须对要使用的绑定进行硬编码。但是,如果服务支持 MEX 端点,则客户端可以发现 MEX 端点地址,然后继续检索并处理元数据以获取要使用的绑定以及端点地址。为了帮助实现 MEX 端点发现,FindCriteria 类提供了静态方法 CreateMetadataExchangeEndpointCriteria:

public class FindCriteria
{
   public static FindCriteria CreateMetadataExchangeEndpointCriteria();
//More members
}

要简化此过程,请使用我的 DiscoveryFactory.CreateChannel<T> 方法:

public static class DiscoveryFactory
{
   public static T CreateChannel<T>(Uri scope = null);
   //More members
}

使用 CreateChannel<T>,图 4 将简化为:

IMyContract proxy = DiscoveryFactory.CreateChannel<IMyContract>(); 
proxy.MyMethod();
(proxy as ICommunicationObject).Close();

CreateChannel<T> 假定 MEX 端点具有一个基数(即,在本地网络中仅找到一个可发现的 MEX 端点),并且假定元数据仅包含一个其约定是指定类型参数 T 的端点。

请注意:CreateChannel<T> 为端点绑定和地址均使用 MEX 端点。服务应该均支持 MEX 端点和发现端点,尽管客户端从不使用发现端点查找实际端点。

如果存在多个支持所需服务约定的服务,或存在多个 MEX 端点,DiscoveryFactory 也会提供 CreateChannels<T> 方法:

public static class DiscoveryHelper
{
   public static T[] CreateChannels<T>(bool inferBinding = true);
   //More members
}

默认情况下,CreateChannels<T> 会根据服务端点的架构推断出要使用的绑定。如果 inferBinding 失败,它将从 MEX 端点发现绑定。

对于兼容的服务端点或 MEX 端点,CreateChannels<T> 不假定一个基数,将返回包含所有兼容的端点的数组。

通知

从服务的角度来看,迄今提供的发现机制都具有被动性。客户端查询发现端点,然后服务进行响应。作为此被动地址发现的替代方法,WCF 提供了一个活动模型,其中服务将其状态传递给所有客户端并提供其地址。当打开服务主机时,它将广播“您好”通知;当正常关闭该主机时,将广播“再见”通知。如果意外中断该主机,则不会发送“再见”通知。这些通知在客户端承载的特殊通知端点上接收(请参见图 6)。

图 6 通知体系结构

image: The Announcement Architecture

通知是独立的端点级别机制,而不是主机级别机制。主机可以选择要通知哪个端点。每个通知都包含端点地址、其作用域及其约定。

请注意:通知与地址发现没有关系。主机可能根本就不支持发现端点,所以不需要发现行为。或者,主机可能选择支持发现端点并通知其端点,如图 6 中所示。

主机可以自动通知其端点。您只需要为发现行为提供有关客户端通知端点的信息。例如,使用配置文件时:

<behavior>
   <serviceDiscovery>
      <announcementEndpoints>
         <endpoint 
            kind = "udpAnnouncementEndpoint"
         />
      </announcementEndpoints>
   </serviceDiscovery>
</behavior>

My EnableDiscovery 扩展方法也将通知端点添加到发现行为。

为便于客户端使用,WCF 使用 AnnouncementService 类提供了通知端点的预存储实现,如图 7 中所示。

图 7 WCF 实现通知端点

public class AnnouncementEventArgs : EventArgs
{
   public EndpointDiscoveryMetadata EndpointDiscoveryMetadata
   {get;}
   //More members
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
                 ConcurrencyMode = ConcurrencyMode.Multiple)]
public class AnnouncementService : ...
{
   public event EventHandler<AnnouncementEventArgs> OfflineAnnouncementReceived;
   public event EventHandler<AnnouncementEventArgs> OnlineAnnouncementReceived;
   //More members 
}

AnnouncementService 是针对并发访问配置的单一实例。AnnouncementService 提供了两个客户端可以订阅的事件委托,以便客户端接收通知。客户端应使用 ServiceHost 的构造函数托管 AnnouncementService,该构造函数可以接受单个实例。这是必要的,以便客户端能够与实例交互并订阅事件。此外,客户端必须将 UDP 通知端点添加到主机:

AnnouncementService announcementService = new AnnouncementService();
announcementService.OnlineAnnouncementReceived  += OnHello; 
announcementService.OfflineAnnouncementReceived += OnBye;
ServiceHost host = new ServiceHost(announcementService);
host.AddServiceEndpoint(new UdpAnnouncementEndpoint());
host.Open();
void OnHello(object sender,AnnouncementEventArgs args)
{...}   
void OnBye(object sender,AnnouncementEventArgs args)
{...}

此重要细节与接收通知有关。客户端将接收 Intranet 中的所有服务的全部通知 ,而不考虑服务的约定类型或应用程序或作用域。客户端必须筛选出相关通知。

简化通知

对于客户端使用通知所需的原始步骤,可通过我的 AnnouncementSink<T> 类大大简化并改进,该类定义如下:

public class AnnouncementSink<T> : AddressesContainer<T> where T: class
{
   public event Action<T> OnHelloEvent;
   public event Action<T> OnByeEvent;
}

AnnouncementSink<T> 通过封装图 7 的步骤使托管通知端点自动化。AnnouncementSink<T> 在内部托管了一个 AnnouncementService 实例的同时还改进了自身的不足之处。首先,AnnouncementSink<T> 提供了两个通知事件委托。与原始 AnnouncementService 不同,AnnouncementSink<T> 可同时引发这些委托。此外,AnnouncementSink<T> 禁用了 AnnouncementService 的同步上下文关联,所以它可接受任何传入线程上的通知,真正实现并发。

AnnouncementSink<T> 筛选约定类型,并仅当兼容端点通知自身时引发 AnnouncementSink<T> 事件。客户端只需打开和关闭 AnnouncementSink<T>,就可指示何时开始和停止接收通知。

AnnouncementSink<T> 派生自我的通用地址容器,名为 AddressesContainer<T>。

AddressesContainer<T> 是一个丰富的地址管理帮助程序集合,可用于操作多个地址。AddressesContainer<T> 支持多个迭代器、索引器、转换方法和查询。

图 8 演示使用 AnnouncementSink<T>。

图 8 使用 AnnouncementSink<T>

class MyClient : IDisposable
{
   AnnouncementSink<IMyContract> m_AnnouncementSink;
   public MyClient()
   {
      m_AnnouncementSink = new AnnouncementSink<IMyContract>();
      m_AnnouncementSink.OnHelloEvent += OnHello; 
      m_AnnouncementSink.Open();
   }
   void Dispose()
   {
      m_AnnouncementSink.Close();
   }
   void OnHello(string address)
   {     
      EndpointAddress endpointAddress = new EndpointAddress(address);
   IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel(
      new NetTcpBinding(),endpointAddress);
      proxy.MyMethod();
      (proxy as ICommunicationObject).Close();
   } 
}

MEX Explorer

在我编写的《Programming WCF Services Second Edition》(O’Reilly,2008)一书中介绍了一个工具,我将其称之为 MEX Explorer(请参见图 9)。可以将 MEX 地址提供给 MEX Explorer,并使用 MEX Explorer 反射服务端点(其地址、绑定属性和约定)。通过采用发现,我可以修改 MEX Explorer。

图 9 MEX Explorer

image: The MEX Explorer

通过单击“Discover”(发现)按钮,可触发对所有 MEX 端点的发现请求,而没有基数限制。然后,此工具 以树形显示所有已发现端点。此外,MEX Explorer 还可利用 MEX 端点的通知。为响应 通知,MEX Explorer 刷新自身并 显示新的端点或从树中删除不再运行的端点。

发现驱动的发布-订阅模式

在 2006 年 10 月的文章《单向调用、回调以及事件须知》中,我介绍了一种自己开发的框架,用于支持 WCF 中的发布-订阅模式。此外,您可以使用发现和通知机制再提供另一种方法来实现发布-订阅系统。

与该文章中的技术不同,基于发现的解决方案是唯一的发布-订阅案例,无需订阅者或管理员执行显式步骤。在使用发现时,无需在代码或配置文件中进行显式订阅。因此,大大简化了系统部署,并在显示发布者和订阅者时提供很大的灵活性。可以轻松地添加或删除订阅者和发布者,而不需执行其他管理步骤或编程。

当在发布-订阅系统中使用发现时,订阅者可以提供发现端点,以便由发布-订阅服务发现;或者可以通知他们的事件处理端点;甚至执行上述两个操作。

发布者不应直接发现订阅者,因为可能导致在每次引发事件(基数为所有端点)时产生发现延迟。而是应该发现发布-订阅服务,该操作为一次性操作且几乎没有成本消耗。发布-订阅服务应是单一实例(可启用快速发现,因为它的基数为一)。发布-订阅服务公开与订阅者相同的事件端点,所以对于发布者该服务貌似元订阅者。也就是说,在发布-订阅服务中,需要使用与实际订阅者相同的代码引发事件。

发布-订阅服务的事件端点必须使用特殊作用域。通过此作用域,发布者可以发现发布-订阅服务而不是订阅者。除支持发现设置作用域的事件端点之外,发布-订阅服务还提供通知端点。

发布-订阅服务维护所有订阅者的列表。发布-订阅服务使用某种持续的后台活动,不断尝试发现订阅者,从而能够不断更新订阅者列表。请再次注意:使发布-订阅服务的事件端点与特殊作用域关联,也可防止该服务在发现所有事件端点时发现自身。发布-订阅服务还可以提供通知端点,用于监视订阅者。图 10 描述了此体系结构。

图 10 发现-驱动的发布-订阅系统

image: Discovery-Driven Publish-Subscribe System

发布-订阅服务

为促进您自己的发布-订阅服务部署,我编写了 DiscoveryPublishService<T>,其定义如下:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class DiscoveryPublishService<T> : IDisposable where T: class
{
   public static readonly Uri Scope;
   protected void FireEvent(params object[] args);
   //More members
}

您只需从 DiscoveryPublishService<T> 派生出发布-订阅服务,并将事件约定指定为类型参数。然后,通过调用 FireEvent 方法实现事件约定的操作。

例如,请考虑以下事件约定:

[ServiceContract]
interface IMyEvents
{
   [OperationContract(IsOneWay = true)]
   void OnEvent1();
   [OperationContract(IsOneWay = true)]
   void OnEvent2(int number);
}

图 11 显示如何使用 DiscoveryPublishService<T> 实现发布-订阅服务。

图 11 实现发布-订阅服务

class MyPublishService : DiscoveryPublishService<IMyEvents>,IMyEvents
{
   public void OnEvent1()
   {
      FireEvent();
   }
   public void OnEvent2(int number)
   {
      FireEvent(number);
   }
}

在内部,DiscoveryPublishService<T> 使用我的另一个 AddressContainer<T> 派生类,名为 DiscoveredServices<T>,其定义如下:

public class DiscoveredServices<T> : AddressesContainer<T> where T: class
{
   public DiscoveredServices();
   public void Abort();
}

DiscoveredServices<T> 旨在保留所有已发现服务的尽可能最新的列表,并将其发现的地址存储在其基类中。DiscoveredServices<T> 在后台线程上分拆出一个持续发现,用于查找已发现地址的当前存储库。

FireEvent 方法从消息标头提取操作名称。然后,该方法查询所有不支持发布-订阅作用域的订阅者的列表,从而避免自身发现。之后,FireEvent 将这些列表合并到唯一条目的集合,这是处理既可通知自身又可发现的订阅者所需的。对于每个订阅者,FireEvent 都会从地址方案推断出绑定并创建在遇到订阅者时引发的代理。使用线程池的线程可并发发布这些事件。

要托管发布-订阅服务,请使用 DiscoveryPublishService<T> 的静态帮助程序方法 CreateHost<S>:

public class DiscoveryPublishService<T> : IDisposable where T: class
{
   public static ServiceHost<S> CreateHost<S>() 
     where S : DiscoveryPublishService<T>,T;
   //More members
}

类型参数 S 是 DiscoveryPublishService<T> 的子类,T 是事件约定。CreateHost<S> 返回您需要打开的服务主机的实例:

ServiceHost host = DiscoveryPublishService<IMyEvents>.
  CreateHost<MyPublishService>();
host.Open();

此外, CreateHost<S> 还将获取一个可用的 TCP 基址并添加事件端点,所以无需配置文件。

发布者

发布者需要事件服务代理。为此,请使用我的 DiscoveryPublishService<T>.CreateChannel:

public class DiscoveryPublishService<T> : IDisposable where T : class
{
   public static T CreateChannel();
   //More members
}

DiscoveryPublishService<T>.CreateChannel 发现发布-订阅服务并创建该服务的代理。该发现速度很快,因为基数是一。发布者的代码非常简单:

IMyEvents proxy = DiscoveryPublishService<IMyEvents>.CreateChannel();
proxy.OnEvent1();
(proxy as ICommunicationObject).Close();

当实现订阅者后,就不需要特殊操作了。只需支持有关服务的事件约定,并添加事件端点的发现或通知,或添加两者。

Juval Lowy 是一名 IDesign 软件架构师,他举行了 WCF 培训并提供了 WCF 体系结构咨询服务。他最新出版了《Programming WCF Services Third Edition》(O’Reilly,2010)。另外,他还是 Microsoft 硅谷地区的区域总监。您可以通过 www.idesign.net 与 Lowy 联系。