# 跟我一起学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)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结