使用发现代理
使用服务声明可以减少由Probe消息和ProbeMatch消息带来的网络流量,但是如果服务启动时客户端程序没有运行那么会发生什么?答案是客户端将失去通知消息因此不能发现服务,因为客户端不能连接到服务。解决方法是使用一个发现消息和声明的结合体;创建一个中间服务,该服务使用一个已知的地址侦听来自服务的声明消息并缓存这些声明消息。在客户端程序中,发送Probe消息至中间服务。中间服务可以接受这些Probe请求,在缓存中查找服务的地址,如果匹配,想客户端返回一条ProbeResponse消息,该消息包含匹配服务的详细信息。由于中间服务在一个固定的、已知的地址上,客户端不需要向网络广播大量的Probe消息;相反,它们直接向中间服务发送单播Probe消息。
在WCF中,中间服务也称之为发现代理。更进一步讲,System.ServiceModel.Discovery命名空间包含了一个抽象类DiscoveryProxy,你可以使用该类来实现一个代理服务。
为了构建代理服务,你首先继承DiscoveryProxy类,然后重载下列抽象方法。这些抽象方法实现异步操作,并且你必须确保它们是线程安全的。
-
OnBeginOnlineAnnouncement和OnEndOnlineAnnouncement,DiscoveryProxy包含了内建的声明服务,该服务用以侦听声明消息。在上一个练习中当使用AnnouncementService类时,你为事件OnlineAnnouncementReceived和OfflineAnnouncementReceived添加了事件处理器。DiscoveryProxy类没有采用相同的方式对外公开这些事件,但你需要为这些事件提供逻辑已使它们发生时可以调用。当服务表明自己开始运行时OnBeginOnlineAnnounce方法开始运行。该方法的目的是存储服务的元数据,无论哪种存储方式(存储在内存中、写入到文件中或者保存在数据库中)都是适合发现服务的。这可能需要耗费一定的时间,因为该方法使用异步编程设计模式实现。该方法的参数包括:EndpointDiscoveryMetadata描述服务的元数据;一个用于当OnEndOnlineAnnouncement方法结束时回调DiscoveryProxy对象AsyncCallback对象。该方法返回一个IAsyncResult对象。DiscoveryProxy对象调用OnEndOnlineAnnouncement方法以结束操作。在IAsyncResult对象表明服务声明已经被处理而且服务的详细信息已经保存前该方法将一直被阻塞。
- OnBeginOfflineAnnouncement和OnEndOfflineAnnouncement,这两个方法提供处理逻辑处理当服务关闭时发送的离线声明消息。这两个方法的目的是从存储中移除服务的详细消息。这两个方法同样使用异步编程设计模式实现。
- OnBeginFind和OnEndFind,当客户端发送Probe消息时这两个方法运行。客户端寻找的服务的相信信息通过FindRequestContext对象传入。OnBeginFind方法指明搜索一个服务,在搜索完成之前OnEndFind方法将一直被阻塞。
- OnBeginResolve和OnEndResolve,WS-Discovery协议同样支持作为Probe消息而引申的Resolve消息;一条Probe消息指定客户端搜索的服务合约,与之相关联的ProbeMatch消息包含服务的地址。一条Resolve请求指定服务的地址,与之对应的ResolveMatch消息包含描述服务合约的元数据。当客户端程序发送一条Resolve消息时OnBeginResolve和OnEndResolve方法开始运行。OnBeginResolve方法异步地执行搜索操作,在搜索完成前OnEndResolve方法将一直被阻塞。
在下面的一组练习中,你将通过扩展DiscoveryProxy类实现发现代理,然后你修改客户端程序发送Probe请求至发现代理而不是试图捕获服务声明。
练习:实现发现代理
1. 在解决方案窗口,添加一个新的控制台应用程序项目ProductsServiceProxy到解决方案ProductsService,然后保存该项目到*\WCF\Step.by.Step\Solutions\Chapter14\ProductsService文件夹下。
2. 添加System.ServiceModel和System.ServiceModel.Discovery组件至ProductsServiceProxy项目。
3. 添加本书源代码中附带的AsyncResult.cs文件;该文件与第十二章的AsyncResult.cs是一样的。
4. 添加一个新的类文件ProductsServiceProxy,其保存在ProductsServiceProxy项目中。
5. 在ProductsServiceProxy.cs文件中,添加下面的using语句之文件头部。
![](https://i-blog.csdnimg.cn/blog_migrate/d09cbe5d6e41923a72388290caeb452d.png)
6. 为ProductsServiceProxy类添加下面的特性
![](https://i-blog.csdnimg.cn/blog_migrate/8a9ca632afbc66557ac9fb255a898829.png)
简单地将,ProductsServiceProxy将在内存中使用ConcurrentDictionary对象缓存服务信息。因此,ProductsServiceProxy服务将使用线程安全的单个服务实例来实现。
7. 修改ProductsServiceProxy类,使其继承DiscoveryProxy类。
![](https://i-blog.csdnimg.cn/blog_migrate/7d6f7d3a93052c7e0f73d7bae33c4d01.png)
8. 在ProductsServiceProxy类中,添加ConcurrentDictionary集合,该集合用于保存服务的信息;在ProductsServiceProxy类的构造函数中对这个集合进行初始化。
![](https://i-blog.csdnimg.cn/blog_migrate/729c6ce85dd4a1644976ae516899780b.png)
9. 为ProductsServiceProxy类添加添加私有的AddService和RemoveService方法。
![](https://i-blog.csdnimg.cn/blog_migrate/577e40d0e6fe2825d24bd2a65e0ff735.png)
10. 添加下面的私有方法FindService到ProductsServiceProxy类
![](https://i-blog.csdnimg.cn/blog_migrate/37924205b7faf017da64d4d97f2645cc.png)
11. 添加私有方法ResolveService到ProductsServiceProxy类
![](https://i-blog.csdnimg.cn/blog_migrate/405ccaed8bce4042511fea3cbef9af61.png)
12. 添加私有方法WaitForAsyncResult方法只ProductsServiceProcy类
![](https://i-blog.csdnimg.cn/blog_migrate/2b54021a543cc951c8ba13374a3620ef.png)
13. 重载DIscoveryProxy类的OnBeginOnlineAnnouncement和OnEndOnlineAnnouncement方法
![](https://i-blog.csdnimg.cn/blog_migrate/1f03ebb8ee461b6cf530bb04d515c055.png)
OnBeginOnlineAnnouncement方法创建Task对象以异步地调用AddServcie方法,该方法添加在参数中指定的服务元数据到services字典中。该方法返回IAsyncResult接口,其包含等待操作结束所需的状态信息。
OnEndOnlineAnnouncement方法接收IAsyncResult对象的参数,然后调用WaitForAsyncResult方法,该方法将阻塞当前线程直到IAsyncResult对象指明操作已经完成。
14. 重载OnBeginOfflineAnnouncement方法和OnEndOfflineAnnouncement方法
![](https://i-blog.csdnimg.cn/blog_migrate/1c9f2e9429c1b51d9db340b2d55ac07f.png)
15. 重载OnBeginFind方法和OnEndFind方法
![](https://i-blog.csdnimg.cn/blog_migrate/561ae5730cc003883e4c1e2127b49459.png)
16. 重载OnBeginResolve和OnEndResolve方法
17. 重新生成解决方案。
你现在已经定义了一个DiscoveryProxy服务,该服务在内存中使用字典中缓存声明请求。下一步就是为该服务提供一个宿主。为了节省时间,并且与使用IIS寄宿有所不同,我们直接使用原书代码中附带的host.cs,你将在下面的联系中检查该宿主程序是否正常工作。
练习:检查ProductsServiceProxy服务是否被寄宿
1. 从ProductsServiceProxy项目中删除Programm.cs文件,然后添加Host.cs文件。
2. 在文本编辑模式下打开Host.cs文件。
Program类包含两个字符串常量probeAddress和announcementAddress。它们均使用tcp地址
![](https://i-blog.csdnimg.cn/blog_migrate/a4de6efaf05992643ada3204d390e804.png)
客户端程序将发送Probe消息至一个在probeAddress地址上侦听的端点,服务将发送声明消息至一个在announcemAddress地址上侦听的端点。
Main方法创建一个用于寄宿ProductsServiceProxy服务的ServiceHost对象
![](https://i-blog.csdnimg.cn/blog_migrate/efd1662013565739673a4807e8fc3d1b.png)
Main方法随后创建一个可发现的端点
![](https://i-blog.csdnimg.cn/blog_migrate/306f78c13e7837c9bcb5ab434abeca52.png)
然后,Main方法使用类似的方式创建一个声明端点
![](https://i-blog.csdnimg.cn/blog_migrate/c5ffc2e7478b95039ce95d9757a27024.png)
接着,Main方法添加上述的两个端点至ServiceHost对象,并开始寄宿她们。此时,可发现带来可以接收来自服务的声明消息和处理来自客户端程序的Probe和Resolve请求。
![](https://i-blog.csdnimg.cn/blog_migrate/fd730466fe99a41e903c200a4bf0b503.png)
3. 重新生病解决方案
在开始运行ProductsService服务之前,你需要配置其向发现代理的TCP端点发送声明消息,而不是在网络中广播声明消息。
练习:配置ProductsService服务发送声明消息至发现代理
1. 使用WCF服务配置管理工具打开C:\...\ProductsService\项目下的web.config文件。
2. 在配置面板,展开高级文件夹,展开服务行为文件夹,展开未命名行为,展开serviceDiscovery行为元素,展开Announcement端点文件夹,然后添加未命名声明端点。
服务当前被配置为通过UDP广播声明消息。
3. 在客户端端点面板,更改Kind属性为announcementEndpoint。
announcementEndpoint端点是一个标准端点,其用于发送多播声明消息。你可以更改Address属性的值以指定声明消息的发送的目标地址。
4. 在地址属性处,输入net.tcp://localhost:8002/Announcement。前面我们提到过,该地址用于侦听来自服务的声明消息。设置绑定属性的值为netTcpBinding,这是因为发现代理使用TCP地址。
![](https://i-blog.csdnimg.cn/blog_migrate/f61c771bc7dc44dbb7f4ba2e1c2f5dc3.png)
5. 保存配置文件,并关闭WCF服务配置管理工具。
同样,你还需要修改客户都应用程序。上一个联系中,该客户端包括了侦听声明消息的功能,但是现在发现代理已经有了该功能。因为现在客户端只需要直接向发现代理发送probe消息,并且不再使用广播的方式。
练习:修改客户端程序发送Probe请求至发现代理
1. 打开ProductsClient项目下的Programm.cs文件
2. 在Program类中,删除ConcurrentDictionary集合的定义services,然后声明probe消息的发送地址。
![](https://i-blog.csdnimg.cn/blog_migrate/a50cb14bde2ec961abba8579609eadca.png)
3. 在Main方法中,移除announcementService变量并删除实现OnlineAnnouncementReceived和OfflineAnnouncementReceived事件。同样移除announcementHost变量以及添加UDP声明端点和侦听的代码。 现在Main方法看起来如同下图所示.
![](https://i-blog.csdnimg.cn/blog_migrate/94fb2488fbff58fff8c75ad9ace7092c.png)
6. 更改Console.WriteLine声明,更改其消息为"Press ENTER when the Discovery Proxy has started"
5. 在Console.ReadLine语句后,添加下面的代码
![](https://i-blog.csdnimg.cn/blog_migrate/e81cba7f9d181d071acff310a5248551.png)
上述代码用于创建一个用于连接至发现代理TCP端点;然后实例化DiscoveryClient对象以连接至刚创建的端点。
6. 接下来,我们需要基于ProductsService实现的服务合约创建FindCriteria对象。
![](https://i-blog.csdnimg.cn/blog_migrate/1a23a19ec7d23f37cbe795dd624900f5.png)
7. 剩余的代码不需要更改。重新生成解决方案。
现在你可以测试发现代理,但是首先你需要部署更新的后的ProductsService服务并配置项目启动发现服务项目和客户端程序。
练习:测试发现代理
1. 在解决方案窗口,在C:\...\ProductsService\项目上点击右键,然后点击发布站点。发布当前的web站点到http://localhost/DiscoverableService并且允许Visual Studio覆盖现有的文件。
2. 在ProductsService解决方案中,设置ProductsServiceProxy和ProductsClient项目为启动项目。
3. 在非调适模式下运行解决方案,但是不要在客户端控制台窗口中按ENTER键。
4. 返回到IIS管理器中,通过浏览ProductsService.svc文件启动DiscoverableProductsService Web应用程序。然后关闭IE浏览器,但保持IIS管理器处于打开状态。
5. 切换到发现代理控制台窗口。你将看到追踪消息OnBeginOnlineAnnouncement和OnEndOnlineAnnouncement方法由ProductsService服务启动而触发,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/a1e550e95c7168aa6c59c7c02e5dd17b.png)
6. 切换到客户端程序控制台窗口,然后按下ENTER键。客户端程序将向发现大力发送Probe消息,然后返回ProductsService服务的地址。客户端程序随后使用该地址与ProductsService服务建立连接并获取和更新产品信息。
7. 返回发现大力控制台窗口,现在你可以看到由客户端发送Probe消息而产出的OnBeginFind和OnEndFind方法的追踪信息:
![](https://i-blog.csdnimg.cn/blog_migrate/ba3603de655484d9087f89300bfee9f6.png)
8. 返回到IIS管理器中。在控制面板,在DiscoverableProductsService服务所使用的应用程序池上点击Recycle。该行为将关闭使用该应用程序池的所有Web应用程序。
9. 切换回发现代理控制台窗口,我们可以看到窗口将会显示OnBeignOfflineAnnouncement和OnEndOfflineAnnouncement方法;ProductsServcie服务声明自己将因为应用程序池关闭而离线。
10. 在发现代理控制台窗口按下ENTER键,然后返回到Visual Studio中。
![](https://i-blog.csdnimg.cn/blog_migrate/0d9a8655fc2f1a28d3041d4dfa7cbf8b.png)