我的WCF之旅(3):在WCF中实现双工通信
2007-03-02 17:17
896 查看
双工(Duplex)模式的消息交换方式体现在消息交换过程中,参与的双方均可以向对方发送消息。基于双工MEP消息交换可以看成是多个基本模式下(比如请求-回复模式和单项模式)消息交换的组合。双工MEP又具有一些变体,比如典型的订阅-发布模式就可以看成是双工模式的一种表现形式。双工消息交换模式使服务端回调(Callback)客户端操作成为可能。
一、两种典型的双工MEP
1.请求过程中的回调
这是一种比较典型的双工消息交换模式的表现形式,客户端在进行服务调用的时候,附加上一个回调对象;服务在对处理该处理中,通过客户端附加的回调对象(实际上是调用回调服务的代理对象)回调客户端的操作(该操作在客户端执行)。整个消息交换的过程实际上由两个基本的消息交换构成,其一是客户端正常的服务请求,其二则是服务端对客户端的回调。两者可以采用请求-回复模式,也可以采用单向(One-way)的MEP进行消息交换。图1描述了这样的过程,服务调用和回调都采用请求-回复MEP。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201505/9478ab09da897584b88cf5bcc20c9617.jpg)
图1 请求过程中的回调
2.订阅-发布
订阅-发布模式是双工模式的一个典型的变体。在这个模式下,消息交换的双方变成了订阅者和发布者,若干订阅者就某个主题向发布者申请订阅,发布者将所有的订阅者保存在一个订阅者列表中,在某个时刻将主题发送给该主题的所有订阅者。实际上基于订阅-发布模式的消息交换也可以看成是两个基本模式下消息交换的组合,申请订阅是一个单向模式的消息交换(如果订阅者行为得到订阅的回馈,该消息交换也可以采用请求-回复模式);而主题发布也是一个基于单向模式的消息交换过程。订阅-发布消息交换模式如图2所示。
我们试图实现的是通过在服务端回调客户端操作的方式实现运算结果的输出。客户端调用CalculatorService正常的服务调用,那么在服务执行过程中借助于客户端在服务调用时提供的回调对象对客户端的操作进行回调,从本质上讲是另外一种形式的服务调用。WCF采用基于服务契约的调用形式,客户端正常的服务调用需要服务契约,同理服务端回调客户端依然需要通过描述回调操作的服务契约,我们把这种服务契约称为回调契约。回调契约的类型通过ServiceContractAttribute特性的CallbackContract属性进行指定。
上面代码中服务契约ICalculator的回调契约ICallback定义如下。由于回调契约本质也是一个服务契约,所以定义方式和一般意义上的服务契约基本一样。有一点不同的是,由于定义ICalculator的时候已经通过[ServiceContract(CallbackContract=typeof(ICallback))]指明ICallback是一个服务契约了,所以ICallback不再需要添加ServiceContractAttribute特性。ICallback定义了一个服务操作DisplayResult用于显示运算结果(前两个参数为执行加法运算的操作数),由于服务端不需要回调的返回值,索性将回调操作也设为单向方法。
步骤二:实现服务
在实现了上面定义的服务契约ICalculator的服务CalculatorService中,实现了Add操作,完成运算和结果显示的工作。结果显示是通过回调的方式实现的,所以需要借助于客户端提供的回调对象(该对象在客户端调用CalculatorService的时候指定,在介绍客户端代码的实现的时候会讲到)。在WCF中,回调对象通过当前OperationContext的GetCallback<T>方法获得(T代表回调契约的类型)。
注: OperationContext在WCF中是一个非常重要、也是一个十分有用的对象,它代表服务操作执行的上下文。我们可以通过静态属性Current(OperationContext.Current)得到当前的OperationContext。借助OperationContext,我们可以在服务端或者客户端获取或设置一些上下文,比如在客户端可以通过它为出栈消息(outgoing message)添加SOAP报头,以及HTTP报头(比如Cookie)等。在服务端,则可以通过OperationContex获取在客户端设置的SOAP报头和HTTP报头。关于OperationContext的详细信息,可以参阅MSDN在线文档。
步骤三:服务寄宿
我们通过一个控制台应用程序完成对CalculatorService的寄宿工作,并将所有的服务寄宿的参数定义在配置文件中。由于双工通信依赖于一个双工的信道栈,即依赖于一个能够支持双工通信的绑定,在此我们选用了NetTcpBinding。
注: 在WCF预定义绑定类型中,WSDualHttpBinding和NetTcpBinding均提供了对双工通信的支持,但是两者在对双工通信的实现机制上却有本质的区别。WSDualHttpBinding是基于HTTP传输协议的;而HTTP协议本身是基于请求-回复的传输协议,基于HTTP的通道本质上都是单向的。WSDualHttpBinding实际上创建了两个通道,一个用于客户端向服务端的通信,而另一个则用于服务端到客户端的通信,从而间接地提供了双工通信的实现。而NetTcpBinding完全基于支持双工通信的TCP协议。
步骤四:实现回调契约
在客户端程序为回调契约提供实现,在下面的代码中CalculateCallback实现了回调契约ICallback,在DisplayResult方法中对运算结果进行输出。
步骤五:服务调用
接下来实现对双工服务的调用,下面是相关的配置和托管程序。在服务调用程序中,通过DuplexChannelFactory<TChannel>创建服务代理对象,DuplexChannelFactory<TChannel>和ChannelFactory<TChannel>的功能都是一个服务代理对象的创建工厂,不过DuplexChannelFactory<TChannel>专门用于基于双工通信的服务代理的创建。在创建DuplexChannelFactory<TChannel>之前,先创建回调对象,并通过InstanceContext对回调对象进行包装。
在服务寄宿程序启用的情况下,运行客户端程序后,通过服务端执行的运算结果会通过回调客户端的操作显示出来,下面是最终输出的结果。
三、特别注意
接下来我们将针对上面这个案例,讨论一些关于双工服务的细节性问题。
问题1:回调对双工信道的依赖
在本案例中,由于使用的NetTcpBinding,所以我们底层采用的是TCP协议。由于TCP协议是一个基于连接的传输协议,只有当通信双方的连接被成功创建出来后,他们之间才能进行正常的消息传输。
在上面给出的客户端代码中,在调用了Add方法后添加了这样的语句“Console.Read();”,这是为了阻止调用proxy的Dispose方法,因为该方法将会试图关闭底层的TCP连接。由于服务端的回调操作也会使用该TCP连接,如果在回调操作尚未执行完毕就试图关闭网络连接,将会导致回调无法正常执行。所以如果我们将该语句去掉,将会抛出如图4所示的ProtocolException异常。
问题3:如果采用WsDualHttpBinding?
接下来我们来看关于双工服务的第3个问题。我们这个案例采用NetTcpBinding作为终结点的绑定类型。现在我们采用基于HTTP的WSDualHttpBinding看看我们的应用能否正常运行。我们需要做的仅仅是改变服务端和客户端的配置。
如果你的IIS的版本是V6或者V7,你的程序运行将一切正常。但是如果还在使用XP操作系统,使用IIS 5.X,会抛出如图6所示的AddressAlreadyInUseException异常。
一、两种典型的双工MEP
1.请求过程中的回调
这是一种比较典型的双工消息交换模式的表现形式,客户端在进行服务调用的时候,附加上一个回调对象;服务在对处理该处理中,通过客户端附加的回调对象(实际上是调用回调服务的代理对象)回调客户端的操作(该操作在客户端执行)。整个消息交换的过程实际上由两个基本的消息交换构成,其一是客户端正常的服务请求,其二则是服务端对客户端的回调。两者可以采用请求-回复模式,也可以采用单向(One-way)的MEP进行消息交换。图1描述了这样的过程,服务调用和回调都采用请求-回复MEP。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201505/9478ab09da897584b88cf5bcc20c9617.jpg)
图1 请求过程中的回调
2.订阅-发布
订阅-发布模式是双工模式的一个典型的变体。在这个模式下,消息交换的双方变成了订阅者和发布者,若干订阅者就某个主题向发布者申请订阅,发布者将所有的订阅者保存在一个订阅者列表中,在某个时刻将主题发送给该主题的所有订阅者。实际上基于订阅-发布模式的消息交换也可以看成是两个基本模式下消息交换的组合,申请订阅是一个单向模式的消息交换(如果订阅者行为得到订阅的回馈,该消息交换也可以采用请求-回复模式);而主题发布也是一个基于单向模式的消息交换过程。订阅-发布消息交换模式如图2所示。
using System.ServiceModel; namespace Artech.DuplexServices.Contracts { [ServiceContract(Namespace="http://www.artech.com/", CallbackContract=typeof(ICallback))] public interface ICalculator { [OperationContract(IsOneWay=true)] void Add(double x, double y); } }
我们试图实现的是通过在服务端回调客户端操作的方式实现运算结果的输出。客户端调用CalculatorService正常的服务调用,那么在服务执行过程中借助于客户端在服务调用时提供的回调对象对客户端的操作进行回调,从本质上讲是另外一种形式的服务调用。WCF采用基于服务契约的调用形式,客户端正常的服务调用需要服务契约,同理服务端回调客户端依然需要通过描述回调操作的服务契约,我们把这种服务契约称为回调契约。回调契约的类型通过ServiceContractAttribute特性的CallbackContract属性进行指定。
上面代码中服务契约ICalculator的回调契约ICallback定义如下。由于回调契约本质也是一个服务契约,所以定义方式和一般意义上的服务契约基本一样。有一点不同的是,由于定义ICalculator的时候已经通过[ServiceContract(CallbackContract=typeof(ICallback))]指明ICallback是一个服务契约了,所以ICallback不再需要添加ServiceContractAttribute特性。ICallback定义了一个服务操作DisplayResult用于显示运算结果(前两个参数为执行加法运算的操作数),由于服务端不需要回调的返回值,索性将回调操作也设为单向方法。
using System.ServiceModel; namespace Artech.DuplexServices.Contracts { public interface ICallback { [OperationContract(IsOneWay=true)] void DisplayResult(double x, double y, double result); } }
步骤二:实现服务
在实现了上面定义的服务契约ICalculator的服务CalculatorService中,实现了Add操作,完成运算和结果显示的工作。结果显示是通过回调的方式实现的,所以需要借助于客户端提供的回调对象(该对象在客户端调用CalculatorService的时候指定,在介绍客户端代码的实现的时候会讲到)。在WCF中,回调对象通过当前OperationContext的GetCallback<T>方法获得(T代表回调契约的类型)。
using Artech.DuplexServices.Contracts; using System.ServiceModel; namespace Artech.DuplexServices.Services { public class CalculatorService : ICalculator { #region ICalculator Members public void Add(double x, double y) { double result = x + y; ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>(); callback.DisplayResult(x, y, result); } #endregion } }
注: OperationContext在WCF中是一个非常重要、也是一个十分有用的对象,它代表服务操作执行的上下文。我们可以通过静态属性Current(OperationContext.Current)得到当前的OperationContext。借助OperationContext,我们可以在服务端或者客户端获取或设置一些上下文,比如在客户端可以通过它为出栈消息(outgoing message)添加SOAP报头,以及HTTP报头(比如Cookie)等。在服务端,则可以通过OperationContex获取在客户端设置的SOAP报头和HTTP报头。关于OperationContext的详细信息,可以参阅MSDN在线文档。
步骤三:服务寄宿
我们通过一个控制台应用程序完成对CalculatorService的寄宿工作,并将所有的服务寄宿的参数定义在配置文件中。由于双工通信依赖于一个双工的信道栈,即依赖于一个能够支持双工通信的绑定,在此我们选用了NetTcpBinding。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <behaviors> <services> <service name="Artech.DuplexServices.Services.CalculatorService"> <endpoint address="net.tcp://127.0.0.1:9999/CalculatorService" binding="netTcpBinding" contract="Artech.DuplexServices.Contracts.ICalculator" /> </service> </services> </system.serviceModel> </configuration>
注: 在WCF预定义绑定类型中,WSDualHttpBinding和NetTcpBinding均提供了对双工通信的支持,但是两者在对双工通信的实现机制上却有本质的区别。WSDualHttpBinding是基于HTTP传输协议的;而HTTP协议本身是基于请求-回复的传输协议,基于HTTP的通道本质上都是单向的。WSDualHttpBinding实际上创建了两个通道,一个用于客户端向服务端的通信,而另一个则用于服务端到客户端的通信,从而间接地提供了双工通信的实现。而NetTcpBinding完全基于支持双工通信的TCP协议。
using System; using System.ServiceModel; using Artech.DuplexServices.Services; namespace Artech.DuplexServices.Hosting { class Program { static void Main(string[] args) { using (ServiceHost host = new ServiceHost(typeof(CalculatorService))) { host.Open(); Console.Read(); } } } }
步骤四:实现回调契约
在客户端程序为回调契约提供实现,在下面的代码中CalculateCallback实现了回调契约ICallback,在DisplayResult方法中对运算结果进行输出。
using System; using Artech.DuplexServices.Contracts; namespace Artech.DuplexServices.Clients { class CalculateCallback:ICallback { public void DisplayResult(double x, double y, double result) { Console.WriteLine("x + y = {2} when x = {0} and y = {1}", x, y, result); } } }
步骤五:服务调用
接下来实现对双工服务的调用,下面是相关的配置和托管程序。在服务调用程序中,通过DuplexChannelFactory<TChannel>创建服务代理对象,DuplexChannelFactory<TChannel>和ChannelFactory<TChannel>的功能都是一个服务代理对象的创建工厂,不过DuplexChannelFactory<TChannel>专门用于基于双工通信的服务代理的创建。在创建DuplexChannelFactory<TChannel>之前,先创建回调对象,并通过InstanceContext对回调对象进行包装。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <client> <endpoint name="CalculatorService" address="net.tcp://127.0.0.1:9999/CalculatorService" binding="netTcpBinding" contract="Artech.DuplexServices.Contracts.ICalculator" /> </client> </system.serviceModel> </configuration>
using System; using Artech.DuplexServices.Contracts; using System.ServiceModel; namespace Artech.DuplexServices.Clients { class Program { static void Main(string[] args) { InstanceContext instanceContext = new InstanceContext(new CalculateCallback()); using(DuplexChannelFactory<ICalculator> channelFactory = new DuplexChannelFactory<ICalculator>(instanceContext,"CalculatorService")) { ICalculator proxy = channelFactory.CreateChannel(); using (proxy as IDisposable) { proxy.Add(1, 2); Console.Read(); } } } } }
在服务寄宿程序启用的情况下,运行客户端程序后,通过服务端执行的运算结果会通过回调客户端的操作显示出来,下面是最终输出的结果。
x + y = 3 when x = 1 and y = 2
三、特别注意
接下来我们将针对上面这个案例,讨论一些关于双工服务的细节性问题。
问题1:回调对双工信道的依赖
在本案例中,由于使用的NetTcpBinding,所以我们底层采用的是TCP协议。由于TCP协议是一个基于连接的传输协议,只有当通信双方的连接被成功创建出来后,他们之间才能进行正常的消息传输。
在上面给出的客户端代码中,在调用了Add方法后添加了这样的语句“Console.Read();”,这是为了阻止调用proxy的Dispose方法,因为该方法将会试图关闭底层的TCP连接。由于服务端的回调操作也会使用该TCP连接,如果在回调操作尚未执行完毕就试图关闭网络连接,将会导致回调无法正常执行。所以如果我们将该语句去掉,将会抛出如图4所示的ProtocolException异常。
InstanceContext instanceContext = new InstanceContext(new CalculateCallback()); using(DuplexChannelFactory<ICalculator> channelFactory = new DuplexChannelFactory<ICalculator>(instanceContext,"CalculatorService")) { ICalculator proxy = channelFactory.CreateChannel(); using (proxy as IDisposable) { proxy.Add(1, 2); //Console.Read(); } }
using System.ServiceModel; namespace Artech.DuplexServices.Contracts { public interface ICallback { [OperationContract] void DisplayResult(double x, double y, double result); } }
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] public class CalculatorService : ICalculator { //省略实现 }
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] public class CalculatorService : ICalculator { //省略实现 }
问题3:如果采用WsDualHttpBinding?
接下来我们来看关于双工服务的第3个问题。我们这个案例采用NetTcpBinding作为终结点的绑定类型。现在我们采用基于HTTP的WSDualHttpBinding看看我们的应用能否正常运行。我们需要做的仅仅是改变服务端和客户端的配置。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <behaviors> <services> <service name="Artech.DuplexServices.Services.CalculatorService"> <endpoint address="http://127.0.0.1:9999/CalculatorService" binding="wsDualHttpBinding" contract="Artech.DuplexServices.Contracts.ICalculator" /> </service> </services> </system.serviceModel> </configuration>
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <client> <endpoint name="CalculatorService" address=" http://127.0.0.1:9999/CalculatorService" binding=" wsDualHttpBinding" contract="Artech.DuplexServices.Contracts.ICalculator" /> </client> </system.serviceModel> </configuration>
如果你的IIS的版本是V6或者V7,你的程序运行将一切正常。但是如果还在使用XP操作系统,使用IIS 5.X,会抛出如图6所示的AddressAlreadyInUseException异常。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <wsDualHttpBinding> <binding name="MyBinding" clientBaseAddress="http://127.0.0.1:8888/calculatecallback" /> </wsDualHttpBinding> </bindings> <client> <endpoint address="http://127.0.0.1:9999/CalculatorService" binding="wsDualHttpBinding" bindingConfiguration="MyBinding" contract="Artech.DuplexServices.Contracts.ICalculator" name="CalculatorService" /> </client> </system.serviceModel> </configuration>
相关文章推荐
- 我的WCF之旅(3):在WCF中实现双工通信
- 在WCF中实现双工通信
- 我的WCF之旅(3):在WCF中实现双工通信
- 在wcf中实现双工通信
- WCF实现双工通信
- [转载]WCF实现双工通信
- WCF中实现双工通信
- 我的WCF之旅(3):在WCF中实现双工通信
- 在WCF中实现双工通信(转载)
- 我的WCF之旅(3):在WCF中实现双工通信
- [转载]我的WCF之旅(3):在WCF中实现双工通信
- 我的WCF之旅(3):在WCF中实现双工通信
- 我的WCF之旅(3):在WCF中实现双工通信
- 我的WCF之旅(3):在WCF中实现双工通信
- wcf使用netTcpBinding实现双工通信
- 我的WCF之旅(3):在WCF中实现双工通信
- 稳扎稳打Silverlight(58) - 4.0通信之WCF RIA Services: 通过 Domain Service, 以 MVVM 模式实现数据的添加、删除、修改和查询
- 稳扎稳打Silverlight(58) - 4.0通信之WCF RIA Services: 通过 Domain Service, 以 MVVM 模式实现数据的添加、删除、修改和查询
- 稳扎稳打Silverlight(58) - 4.0通信之WCF RIA Services: 通过 Domain Service, 以 MVVM 模式实现数据的添加、删除、修改和查询
- WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[实现篇]