合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
# 跟我一起学WCF(11)——WCF中队列服务详解 ## 一、引言 在前面的WCF服务中,它都要求服务与客户端两端都必须启动并且运行,从而实现彼此间的交互。然而,还有相当多的情况希望一个面向服务的应用中拥有离线交互的能力。WCF通过服务队列的方法来支持客户端和服务之间的离线工作,客户端将消息发送到一个队列中,再由服务对它们进行处理。下面让我们具体看看WCF中的队列服务。 ## 二、WCF队列服务的优势 在介绍WCF队列服务之前,首先需要了解微软消息队列(MSMQ)。MSMQ是在多个不同应用之间实现相互通信的一种异步传输模式,相互通信的应用可以分布在同一台机器,也可以分布在相连的网络环境。它的实现原理是:客户端将消息发送到一个容器中,然后把它保存到一个系统公用空间的消息队列(Message Queue)中,本地或异地的服务再从该队列中取出发送给它的消息进行处理。更多详细内容可以参考我的博文:[跟我一起学WCF(1)——MSMQ消息队列](http://www.cnblogs.com/zhili/p/MSMQ.html)。 WCF框架对MSMQ进行了集成和扩展,MSMQ支持离线消息模式,并且在WCF框架下,提供了基于http桥的internet网络队列服务的调用扩展。从而赋予了WCF队列服务以下几点优势: 1. 支持离线消息模式。因为WCF框架集成了MSMQ,所以WCF队列服务自然也支持离线消息。 2. 支持将操作分解。WCF支持将工作分解为多个操作放入队列中,可改善系统的可用性和吞吐量。 3. 提供对失败的事务做善后处理。当我们的业务事务需要几个小时或几天完成的时候,我们通常将它分为至少2个事务。第一个事务将需要立即完成的工作放入队列,而第二个事务用于验证第一个事务是否成功,并在必要的情况下对失败的事务进行善后处理。 4. 支持负载平衡。可以把过载的客户端请求放入队列,空闲的时候进行处理,这样可以平衡系统的吞吐量,改善性能。 ## 三、WCF队列服务通信框架 WCF使用NetMsmqBinding来支持消息队列通信。当客户端调用服务时,客户端消息会被封装为MSMQ消息,发送到系统公用的消息队列中,服务宿主在运行状态下会启动通道监听器来检测消息队列消息,如果发现对应的消息,则会从队列里取出消息,使用分发器转发给对应的服务,具体的通信框架如下图所示: ![](https://box.kancloud.cn/2016-01-23_56a2eb49a1c68.png) 如果宿主离线,消息会被放入队列,等待下一次宿主联机时,在执行消息分发给指定WCF服务处理。 另外WCF还提供了[MsmqIntegrationBinding](http://msdn.microsoft.com/zh-cn/library/system.servicemodel.msmqintegration.msmqintegrationbinding(v=vs.110).aspx)类,该类用于需要将WCF 应用和现有的基于MSMQ的应用集成的情况。WCF应用可利用该绑定向现有的MSMQ应用程序发生消息,或从这些应用程序接收消息。 ## 四、利用WCF队列服务来实现离线操作 前面介绍WCF队列服务的优势和它的通信框架,下面具体通过一个例子来诠释WCF队列服务的实现。我们还是按照前面文章介绍的三个步骤来实现该实例。 第一步:定义契约和实现服务。具体的实现代码如下所示: ``` 1 [ServiceContract] 2 public interface IWCFMSMQService 3 { 4 // 操作契约,必须为单向操作 5 [OperationContract(IsOneWay = true)] 6 void SayHello(string message); 7 } 8 9 // 契约实现 10 public class WCFMSMQService : IWCFMSMQService 11 { 12 public WCFMSMQService() 13 { 14 Console.WriteLine("WCF MSMQ Service instance was created at: {0}", DateTime.Now); 15 } 16 17 #region IOrderProcessor Members 18 19 [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)] 20 public void SayHello(string message) 21 { 22 Console.WriteLine("Hello! {0},调用WCF操作的时间为:{1}", message, DateTime.Now); 23 } 24 25 #endregion 26 } ``` 上面代码需要注意一点:WCF操作必须定义为单向操作,因为要实现的是一个队列服务,其特点为异步、离线,无返回值。所以要设置IsOneWay属性为true。 第二步:实现宿主。这里仍然使用控制台应用程序作为WCF队列服务的宿主,具体的实现代码如下所示: ``` 1 namespace WCFConsoleHost 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 string path = @".\private$\LearningHardWCFMSMQ"; 8 if (!MessageQueue.Exists(path)) 9 { 10 MessageQueue.Create(path, true); 11 } 12 13 using (ServiceHost host = new ServiceHost(typeof(WCFMSMQService))) 14 { 15 host.Opened += delegate 16 { 17 Console.WriteLine("Service has begun to listen\n\n"); 18 }; 19 20 host.Open(); 21 22 Console.Read(); 23 } 24 } 25 } 26 } ``` 对应的配置信息如下所示: ``` <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="msmqServiceBehavior"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> <bindings> <netMsmqBinding> <binding name="msmqBinding"> <security> <transport msmqAuthenticationMode="None" msmqProtectionLevel="None"/> <message clientCredentialType="None"/> </security> </binding> </netMsmqBinding> </bindings> <services> <service behaviorConfiguration="msmqServiceBehavior" name="WCFContractAndService.WCFMSMQService"> <endpoint address="net.msmq://localhost/private/LearningHardWCFMSMQ" binding="netMsmqBinding" bindingConfiguration="msmqBinding" contract="WCFContractAndService.IWCFMSMQService" /> <!--发布服务元数据的终结点--> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> <host> <baseAddresses> <add baseAddress="http://localhost:9003/" /> </baseAddresses> </host> </service> </services> </system.serviceModel> </configuration> ``` 第三步:WCF客户端的实现。首先以管理员权限启动宿主,然后通过添加服务引用的方式来生成代理客户端类,具体在添加服务引用窗口地址栏输入:http://localhost:9003/mex来添加服务引用,添加服务引用成功后将生成代理类,通过代理类来对WCF服务进行访问,具体的实现代码如下所示: ``` 1 namespace WCFClient 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 WCFMSMQServiceClient proxy = new WCFMSMQServiceClient("NetMsmqBinding_IWCFMSMQService"); 8 using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required)) 9 { 10 Console.WriteLine("WCF First Call at:{0}", DateTime.Now); 11 proxy.SayHello("World"); 12 13 Thread.Sleep(2000);//客户端休眠两秒,继续下一次调用 14 Console.WriteLine("WCF Second Call at:{0}", DateTime.Now); 15 proxy.SayHello("Learning Hard"); 16 17 scope.Complete(); 18 } 19 20 Console.Read(); 21 } 22 } 23 } ``` 经过上面三步,我们就完成了一个WCF队列服务程序。下面让我们看看该程序的运行结果。 因为WCF队列服务是对MSMQ的集成和扩展,所以此时该程序客户端可以在WCF服务离线的情况也可运行,即WCF服务宿主不启动,客户端也可以正常运行,这点与前面介绍的WCF程序完成不同,因为前面的WCF程序,如果宿主程序不启动而直接启动客户端程序,则客户端程序运行时将会发生异常。该程序之所以不会发生异常的原因在于,此时客户端程序是直接与消息队列进行交互的,而不是直接与WCF服务进行交互。此时只运行WCF客户端,你将看到如下图所示的运行结果: ![](https://box.kancloud.cn/2016-01-23_56a2eb49bb826.png) 同时,你在消息队列的专有队列的队列消息中将看到两条未处理的消息信息,具体效果如下图所示: ![](https://box.kancloud.cn/2016-01-23_56a2eb49cba29.png) 因为WCF服务宿主还没有启动,所以客户端发送的消息信息还没有被取出处理,接下来,我们启动下服务宿主来对队列中的消息进行处理,此时你可以选择关闭客户端(当然你也可以不关闭)。具体的WCF服务宿主的运行结果如下图所示: ![](https://box.kancloud.cn/2016-01-23_56a2eb49dfeba.png) 此时,如果刷新消息队列的专有队列中的队列消息,你将看不到任何消息了,这是因为WCF服务从消息队列取出消息进行处理了,即消息完成了出队的操作。 ## 五、总结 到这里,WCF队列服务的介绍也就结束了。本文主要介绍了WCF集成了MSMQ的功能,WCF可以利用netMsmqBinding来实现离线服务。其实现程序与MSMQ程序实现差不多,关于MSMQ的相关内容也可以参考我的另一篇博文:[跟我一起学WCF(1)——MSMQ消息队列](http://www.cnblogs.com/zhili/p/MSMQ.html)。在下一篇博文中将分享WCF对Rest服务的支持和实现。 本文所有源码:[WCFMSMQService.zip](http://files.cnblogs.com/zhili/WCFMSMQService.zip)