WCF 4.0 进阶系列 – 第十二章 实现单向操作和异步操作(上)
当客户端程序调用一个单向操作后,客户端可以继续运行而不用等待服务完成该操作。你可以通过操作合约指定单向操作行为。达到该目的的最简单方式是在当以操作时设置Operation-Contract特性类的IsOneWay属性为true。在本章的练习中你将会看到具体的例子。
单向操作的效果
定义一个操作为单向操作包含多个暗示,其中最重要的暗示为具有单向操作特性的操作不能回传任何数据至客户端程序,该操作的返回值必须为void并且该函数的参数不能标记为out或者ref。当服务的一个单向操作开始运行后,客户端程序既不需要继续与服务联系也不关注操作是成功还是失败。如果操作引起一个异常,一般地该异常将生成一条SOAP错误消息。该SOAP错误消息将被直接而不会发送至客户端。
调用一个单向操作不是简单地生成一条消息,然后把该消息丢给一个服务侦听的端点。尽管客户端程序不知道单向操作合适完成(或许成功或许失败),对于客户端来说服务是否成功地接收到消息仍然非常重要。如果服务没有在指定的端点上侦听,客户端将会接收一个异常警告。此外,如果服务繁忙,服务可能不能立即接受客户端发送的消息。事务为请求实现一个缓冲机制,如果服务已经接收到过多未处理消息,那么服务将不会再接受新消息。比如,TcpTransport通道
有一个属性名为ListenBacklog,通过该属性可以指定等待处理的最大连接请求数。当当等待消息达到最大值,随后的消息将会被阻塞,直到等待消息的数量下降。在这种情况下,客户端程序将一直等待直到请求被服务接受。如果你希望传输不被缓存并且服务肯定接受到消息,那么你应该在一个可靠的会话中实现单向操作。像第十章"实现可靠会话"中描述的那样,当服务开始处理消息时将回传一个收条至客户端。可靠地消息传递在单向操作方面还有其他好处,在本章后续内容中你将会看到。
单向操作与事务
回想第九章"支持事务",客户端初始化一个事务,然后把该事务流至服务的操作中。事务协议允许一个服务指明一个操作失败,然后引起事务终止。这要求服务能把操作的结果信息回传至客户端。然而,当你实现单向操作后,将没有向客户端回传结果信息的通道。因此请你务必记住:当你设计服务时,如果标记一个操作为单向操作,那么该操作的TransactionFlowOpt-ion属性必须为NotAllowed。当你运行一个服务,WCF运行时执行职能检查,如果单向操作的TransactionFlowOption属性不为NotAllowed,那么系统将抛出OperationException异常。
单向操作与超时
当通过网络向服务发送消息时,有许多地方都可能造成发送消息失败。如果客户端没有在规定的时间内接收到请求对应的响应消息,客户端的WCF运行时将假设发生了某个错误并想客户端程序抛出一个System.TimeoutException异常。超时的时间段可以通过客户端使用的绑定的SendTimeout属性在配置文件中配置;该属性的默认值为1分钟。如果客户端调用单向操作,服务仅仅只需要在超时前接受消息,不需要完成消息处理。
在WCF中,需要绑定都为请求实现了缓存模型。回想第七章"维护状态与顺序化操作"中,实现Session的服务默认使用单线程。当客户端程序向实现Session服务发送一条请求,那么该请求将直接进入一个指定客户端连接的Session中。如果该客户端程序通过相同的连接发送第二条消息,那么该消息将会被发送至相同的Session中。如果运行该Session的服务实例仍然在处理第一条消息,那么第二条消息将被缓存并等待被服务实例接受。绑定有一个名为ReceiveTimeout的属性,该属性被WCF运行时用来管理和控制缓存的请求。如果超出该属性指定的时间,服务实例仍然没有接收一个请求,将想客户端抛出一个Timeout异常。ReceiveTimeout的默认值为10分钟。对于客户端程序来讲,等待一个请求被拒绝的时间一般比较长;除非你的操作真正地能在该一个较小的时间段内完成你才可以减少ReceiveTimeout的持续时间。
实现单向操作
在下面的练习中,你将实现一个单项操作,并在客户端调用单向操作时调查发生了什么。你将创建的Web服务为AdventureWorks组织提供管理功能。
你实现的第一个操作为管理员提供一个报表,该报表包含当天的销售数据。该报表可能需要执行几分钟,你不希望管理员一直等待报表的运行,因此,你将使用单向操作实现该特性。
练习:创建AdventureWorks管理操作服务
1. 启动Visual Studio,使用WCF服务模板创建一个新web站点。在创建新站点对话框中,按照下表内容指定该站点的属性。
属性
|
值
|
站点位置
|
文件系统
|
文件夹
|
D:\Works\Solutions\WCF\Step.by.Step\Chapter12\AdventureWorksAdmin
|
2. 在解决方案窗口,选择站点,然后在其属性窗口中,设置动态端口为false,并设置端口为9090。
注意,当你将使用动态端口设置为false之后,需要等待几秒钟的时间,才可以修改端口号。在默认情况下,ASP.NET开发Web服务器运行时选择一个未使用的端口;但是,在本练习中,提前知道服务将使用的准确端口号是非常有用的。经验动态湍口号可以让你指定一个固定的端口。
3. 在解决方案窗口,展开App_Code文件夹,然后打开IService.cs文件。
4. 在IService.cs文件中,移除using语句后的所有代码。
5. 添加一个名为IAdventureWorksAdmin的服务接口之IServcie.cs文件,并设置该接口为服务合约。在ServiceContract特性中,设置其命名空间属性为http://adventure-works.com/2011/08/05,设置其名字为AdministrativeService。
6. 为服务合约IAdventureWorksAdmin添加一个名为GenerateDailySalesReport的操作,并设置该操作的IsOneWay属性为true。
注意该方法的返回值为void。所有的单向操作的返回值都必须为void。单项操作方法可以接收参数,但是参数不能标记为ref或者out。
7. 在解决方案窗口,在Web项目上点击右键,然后点击添加引用。在添加引用对话框中,添加PresentationFramework组件,然后点击确认按钮。
你将在服务中实现消息框,而MessageBox类包含在该组件中。
8. 打开App_Code文件夹下的Service.cs文件,添加一个名为AdventureWorksAdmin的公共类到该文件。该类应该实现IAdventureWorksAdmin接口,并提供GenerateDailySalesReport方法。
当前版本的WCF服务通过停止线程1分钟的方式模拟生产报表的过程。当报表产生后,该方法将显示一个消息对话框。
9. 打开Service.svc文件,修改该文件引用AdventureWorksAdmin类。
10. 在解决方案窗口,使用WCF服务配置编辑器编辑Web.config文件。
11. 在WCF服务配置编辑器的配置面板,点击服务文件夹。在服务面板,电价创建一个新服务。
12. 在创建新服务向导过程中,使用下表的信息定义新创建的服务。
页面 | 提示 | 值 |
服务类型 | Service type< | AdventureWorksAdmin |
服务合约 | Contract | IAdventureWorksAdmin |
通讯模式 | HTTP | |
互操作的方法 | 高级Web服务互操作(单工通讯 | |
端点地址 | Address | http://localhost:9090/AdventureWorksAdmin/Service.svc |
13. 在配置面板中,点击绑定文件夹,在绑定面板中,点击创建新的绑定配置。
14. 在创建绑定对话框中,选择ws2007HttpBinding,然后点击确认按钮。
15. 在右边面板中,在名字属性处,输入AdventureWorksAdminWS2007HttpBindingConfig。
16. 生产每日销售报表的时间不会超过5分钟,因此设置ReceiveTimeout属性的值为00:05:00。
17. 在配置面板,展开AdventureWorksAdmin服务下的端点文件夹,然后点击未命名端点。
18.在服务端点面板,设置BindingConfiguration属性值为AdventureWorksAdminWS2007HttpBindingConfig。
19. 在配置面板,展开高级文件夹,然后展开服务行为文件夹,再展开未命名行为,最后点击servcieDebug行为元素,在ServiceDebug面板,设置IncludeExceptionDetailsInFaults属性为true。
20. 在配置面板,点击serviceMetadata行为元素。在ServiceMetada面板,验证HttpGetEnabled属性为True。
21. 保存配置未见,然后退出服务配置编辑器
22. 打开Web.config文件
23. 在<serviceHostingEnvironment>节点中,设置muitipleSiteBindingsEnabled属性为false.
24. 保存Web.config文件
25. 在解决方案窗口,在Service.svc文件上点击右键,然后点击在浏览器中查看。
ASP.NET开发服务器将启动并在你电脑右下家的任务栏显示一个图标。IE将启动,打开http://localhost:9090/AdventureWorksAdmin/Service.svc。
该页面描述了如何为IAdventureWorksAdmin服务生成WSDL描述;记忆如在在客户端中使用该服务。其内容如下图所示:
26. 关闭IE,然后返回到Visual Studio。
27. 在解决方案窗口,在站点上点击右键然后点击启动选项。在启动选项页面,选择"不打开一个页面,等待外部程序的请求"然后点击确认按钮。
练习:创建客户端测试AdventureWorks管理操作服务
1. 在Visual Studio中,添加一个新的控制台程序项目到AdventureWorkWorksAdmin解决方案中。按照下表内容指定该控制台程序项目的属性。
属性
|
值
|
名字
|
AdventureWorksAdminTestClient
|
文件夹
|
D:\Works\Solutions\WCF\Step.by.Step\Chapter12\
|
2. 在解决方案窗口中,在AdventureWorksAdminTestClient项目上点击右键,然后点击添加服务引用。在添加服务引用对话框中点击自动识别按钮,然后在Namespace文本框中输入AdventureWorksAdmin,然后点击确认按钮。
该行为创建一个代理,客户端程序使用该代理对象联系至AdventureWorksAdmin服务。
3. 打开App.config文件并检查该文件的内容。注意Visual Studio已经添加和配置了一个客户端端点,该端点名为WS2007HttpBing_AdministrativeService。
4. 在解决方案窗口中,在AdventureWorksAdminTestClient项目中,打开Programm.cs文件,然后添加下面的using语句。
5. 添加下面的代码值Main方法中
6. 在解决方案状况中,在AdventureWorksAdmi方案上点击右键,然后点击设置启动项目。在属性页面对话框中,选择多个启动项目。设置两个项目的动作均为启动,然后点击确认按钮。
7. 在非调适模式下启动解决方案
第一个报表的请求非常快速地完成。但是第二个报表请求导致客户端程序停止。如果你等待1分钟,客户端程序将发生超时并抛出错误:
你应该还注意到在1分钟后,AdventureWorksAdmin服务最终完成第一个报表并显示一个消息框:
点击确认按钮以关闭消息框。如果你继续等待1分钟,第二个消息对话框也将出现。这就表明第二个报表的请求实际上被AdventureWorksAdmin服务接收到,尽管客户端程序获得了错误。
点击确认按钮关闭第二个消息框。然后在客户端控制台窗口中按ENTER键关闭客户端控制台程序。
那么,什么导致了客户端的错误?问题的一部分由于服务使用的sessions;另一部分由于服务使用并发模式。在早期的讨论"单向操做与超时"中提到,服务的sessions默认是单线程,因此服务的实例将在处理当前请求时不会接受一个新的请求。如果你禁用sessions或者设置SessionMode为NotAllowed,那么对该服务的并发调用将不会被阻塞;相反,每个调用将被发送至一个不同的服务实例。
在下面的练习中,你将看到如何解决这个问题
练习:解决单项请求的阻塞问题
1. 在Visual Studio中,编辑Service.cs文件。添加ServiceBehavior特性至AdventureWorkAdmin类。
正如在第七章描述的,你可以使用ServiceBehavior特性类的ConcurrencyMode属性更改session使用的线程模式。选择Multiple运行服务在同一个会话中处理多个并发请求,尽管你必须确认你为每个方法写的代码是线程安全的。
2. 在非调适模式下启动解决方案
这次,两个请求都成功的提交。但是客户端程序停止并在main方法结束部分发生超时。
在客户端控制台窗口中按ENTER键关闭客户端控制台程序,然后当两个消息框出现是点击确认按钮。
这次,阻塞是由于服务实现的安全和客户端代理对象调用Close方法结合引起的。WS2007HttpBinding绑定默认使用sessions和消息级别的安全。当终止一个会话,客户端程序和服务交换消息以确保会话以一种安全的和可控制的方式结束。直到消息交换完成前客户端程序都不会结束,而服务不会在操作完成前发送它的最后一条消息。因此,超时发生了。
两个可能的解决方法是禁用安全特性(不推荐)或更改为传输级别的安全。但是,如果你想继续使用消息安全,还有第三种解决方法:配置可靠地会话
3. 使用WCF服务配置编辑器编辑服务站点的Web.config文件。
在配置面板,展开绑定文件夹,然后选择AdventureWorksAdminWS2007HttpBindingConfig绑定配置。在右边面板中,设置ReliableSession的Enabled属性值为True。保存配置文件并退出WCF服务编辑器。
4. 使用WCF服务配置编辑器打开AdventureWorksAdminTestClient项目下的App.config文件,并重复上述步骤。
5. 在非调适模式下运行解决方案。
客户端成功地发送两个请求,然后迅速地关闭与服务之间的会话,并且没有发生超时。在1分钟后,第一个消息框出现;然后很快第二个消息框也出现。
在第10章,你已经了解到通过WCF实现的可靠会话协议发送消息。该协议的目的在于确保客户端和服务两者之间发送的消息都被接收到。当客户端程序关闭会话,客户端不需要等待服务实际完成操作,只要服务想客户端发送一个收条,该收条表明服务已经接收到客户端的所有消息。在客户端接收到服务发送的收条后,在服务真正地结束会话前,客户端代理对象的Close便可以结束。
使用单向操作的推荐
你已经看到单向操作是一个非常由于的机制,它通过允许客户读继续执行而不用等待服务完成请求的操作,以改善客户端程序的相应。但是,为了最大化客户端和服务之间的并发数量,你应该考虑下面这些:
- 默认情况下,不使用sessions的服务提供了最大成都的并行性。如果一个服务要求或者允许sessions,那么将依赖于绑定指定的传输协议如何缓存消息,如果服务在同一个会话中正在处理一条消息,那么可能会阻塞单向请求。即使服务使用PerCall服务实例模式这也可能发生;这是由于服务使用sessions从而导致服务阻塞了请求。
- 使用sessions的服务可以设置服务实例模式为多线程模式,但是你必须确保该服务中的操作是线程安全的。启用多线程允许服务在同一个会话中同时执行多个请求。
- 使用可靠地会话可以确保客户端程序在服务完成处理所有的等待处理的请求前关闭与服务的连接。
此外,恶意用户掌握了如何利用单向操作去发动服务拒绝攻击。他们向一个服务发送大量的请求,其结果会服务在尝试处理所有的消息时发生中断。如果你实现一个允许多线程的并支持异步操作的服务,你不许采取步骤以确保攻击者在一个既定的时间段内不能发送过量的请求数从而导致你的系统由于压力而坍塌。