WCF 4.0 进阶系列 – 第十六章 使用回调合约发布和订阅事件(第一部分)

到目前为止,本书中你看到的练习与例子都集中于C/S模型。在该模型中,一个服务器提供一个服务,该服务等待客户端主动发出的请求,接收到客户端的请求后,处理请求,然后选择性地向客户端程序发送响应。客户端程序是活动的参与者,提交请求并有效地决定了服务何时开始执行工作。然而这只是大多数的情况,WCF还支持其他的处理体系,比如点对点网络和客户端回调。
在点对点场景中,没有被动的服务。所有参与者都是自主的客户端,因为参与者之间可以平等地通讯。此时,不存在客户端/服务器关系,因此参与者任何时候都应当准备处理向其发送的消息。
使用客户端回调,服务可以调用客户端程序中的一个方法,实际上转换了C/S管理中的客户端和服务器,回调时原先的服务端和客户端将发生对调,服务端成为客户端,客户端成为服务端。在本章,你将调查如何定义客户端回调,以及如何使用客户端回调实现一个简单的事件机制,该事件机制用于通知关注服务状态的客户端服务的状态发生了改变。

实现和调用一个客户端的回调

在传统的C/S协议中,当宿主程序打开ServiceHost对象后,服务在一个或者多个由WCF服务架构建立的端点上侦听消息;然而客户端程序可能期望仅仅接收响应消息,而且该响应消息是客户端通过隐式请求发送到客户端的。一旦客户端打开了与服务之间的通道,WCF运行时将激活服务向客户端发送额外的消息(用于建立通讯),当客户端发出一个已经接收到额外消息的声明后,服务停止发送这类额外消息。WCF提供了两个特性来实现该功能:回调合约和双向通道。使用回调时非常重要的一点是:回调仅仅能在客户端向服务发送请求的处理过程中使用;而且还必须在由客户端初始化和用于发送请求的通道中调用回调。

定义一个回调合约

一个回调合约定义了服务可以在客户端中回调的操作。一个回调合约与服务合约非常相似,因为它们都是一个接口或一个类,其包含的操作标记了OperationContract特性。从语义方面来讲接口和类是有区别的,区别在于是否使用ServcieContract特性。下面的例子定义了一个方法,服务可以调用该方法通知客户端产品的价格发生改变。
侦听回调的客户端实现了回调合约中的每个方法。服务识别客户端的回调有两个必要条件:服务实现了服务合约中定义的回调操作,客户端必须引用了该服务合约(实现回调合约的服务合约)。为了达到该目的,你可以使用ServiceContract特性类中的CallbackContract属性标记服务合约,如下面的代码所示:
该代码的目的是:客户端程序调用ChangePrice操作以更新某个特定产品的价格;当产品的价格修改后,服务在客户端调用OnPriceChanged操作,然后回传一个修改后的产品到服务。你应当注意IProductsServiceV3服务合约中的其他操作也可以调用OnPriceChanged操作,这是因为回调合约与服务合约捆绑在一起,而不是与服务合约中的讴歌操作捆绑在一起。

在一个回调合约中实现一个操作

当你试图构建一个客户端代理去访问在服务合约中关联了回调合约的服务时,该代理类必须基于System.ServiceModel.DuplexClientBase类。该代理类的结构大致如下:
上图中黄色方框内的内容显示了与普通代理之间的不同,因为普通的代理不需要定义回调合约。开发人员应创建一个客户端程序,在这个客户端程序中应包含一个类,此类实现了IProductsServiceV3Callback接口和接口的OnPriceChanged方法。
ProductsServiceV3Client代理类扩展了DuplexClientBase<IProductsServiceV3>类,而且它还包含许多构造函数,客户端程序可以使用这些构造函数实例化代理对象。上诉代码片段仅仅显示了其中的两个构造函数,所有构造函数的主要特点都是:第一个参数均为一个InstanceContext对象。这是服务可以调用客户端中操作关键特性。
你现在应该已经非常清楚一个服务中通过InstacneContextMode为该服务指定的"instance context"的含义。如果不清楚也没关系。让我们来回忆一下,服务的每个实例运行在自己的上下文中,并且这些上下文中保存实例的状态信息。服务的每个实例都有自己的上下文。当实例化服务实例时,WCF运行时创建并自动地初始化这些上下文。那么什么时候开始初始化这些上下文呢?下面列出了初始化上下文的三种情况:
  • 客户端程序开始一个新 会话(如果服务指定PerSession实例上下文模式)
  • 客户端调用服务的一个操作(如果服务指定PerCall实例上下文模式)
  • 服务的宿主程序启动服务(如果服务指定Single实例上下文模式)
当客户端连接到一个服务实例时,在客户端和服务之间传输消息的通讯通道中保存了某个特定服务实例的信息,因此WCF运行时可以引导消息到正确的服务实例。
当实现一个客户端回调时,你必须提供相同的功能以使服务端WCF运行时可以把消息路由回正确的客户端。为了实现该目标,你需要创建一个InstanceContext对象,该对象指向一个特定的客户端程序实例,当通过代理连接到服务时把InstaceContext对象传递到服务,客户端的WCF运行时自动地把客户端实例的信息包含在请求消息中,然后把请求消息发送之服务。如果服务需要调用回调合约中的操作,那么服务通过上下文对象直接调用适当的客户端实例。
下面的代码展示了如何在客户端实现客户端代理中IProductsServiceV3Callback接口:
InstanceContext的构造器中的this参数指向实现了IProductsServcieV3Callback合约的对象。DoSomeWork方法中创建proxy对象的语句引用了InstanceContext对象。如果服务通过该InstanceContext对象调用OnPriceChanged操作,那么服务端的WCF运行时将调用该客户端实例中的OnPriceChanged方法。
请注意,CallbackClient类还实现了IDisposable接口;Dispose方法用于关闭代理。一旦客户端连接到服务并且发送了一条初始化消息之后,服务可能在任意时刻回调客户端实例。如果客户端程序在DoSomeWork方法中,向服务发送完请求之后立即关闭代理;那么服务试图回调客户端实例时将失败,因为客户端实例对象不再有效。正如上面的代码所展示,如果客户端实例在DoSomwWork方法之后继续存在,那么在Dispose方法中关闭代理对象能确保服务可以在任意时刻回调客户端实例,除非客户端程序终止或者客户端对象被显示地销毁。

在回调合约中调用一个操作

为了调用回调合约中的一个操作,服务必须获取向服务发送请求的客户端实例的引用。正如你刚刚看到的,服务端的WCF运行时通过该服务的操作上下文获取客户端实例信息。你可以通过惊呆书香OperationContext.Current属性访问操作上下文,该属性返回一个OperationContext对象。OperationContext类提供generic的GetCallbackChannel方法,该方法可以返回一个通道的引用;这个通道就是服务和调用服务的客户端之间进行通讯的那个通道。GetCallbackChannel方法的返回值回调合约类型的引用;你可以通过这个引用调用操作。正如下面的代码所示:
很有可能在客户端调用完服务的操作和服务正在回调客户端的时间内,尤其当客户端通过单向方式调用服务的操作后,客户端开始终止或关闭通讯通道。因此你在服务执行回调之前你应该检查通道回调通道是否已经被关闭,如下面黄色方块中的代码所示:
所有的WCF通道都实现了ICommunicationObject接口。该接口提供了State属性,你可以使用该属性确定通道是否仍然处于打开的状态。如果State属性的值不为CommunicationState.Openned,那么此时服务不应该试图去使用回调。

回调操作与线程

如果服务调用回调合约中的一个操作,那么很可能客户端的代码也实现了该回调合约以使其他的操作能在服务中被回调。默认情况下,服务端的WCF运行时使用单线程处理回调的执行,因此回调至服务可能导致服务阻塞处理初始化请求的那个线程。在这种情况下,WCF运行时探测当前的情形,然后抛出一个InvalidOperationException异常,该异常的消息为"This operation would deadlock because the reply cannot be received until the current Message completes processing"。为了避免出现这种情形的发生,你可以在客户端程序中的回调实现类中设置并发模式:那么启动多线程(如果客户端程序的代码是线程安全的),要么启动重入(如果客户端程序的代码不是线程安全的,但是客户端使用的数据在多次调用之前是一致的)。为了实现这点,你需要在客户端程序中的回调实现类上应用CallbackBehavior特性,设置该特性类的ConcurrencyMode属性的值为ConcurrenyMode.Muitiple或者ConcurrencyMode.Reentrant。
 

绑定和双向通道

并不是所有的绑定都支持客户端回调。为了支持客户端回调,你必须使用支持支持双向通讯的绑定;连接的一端必须能初始化通讯,而另一端必须能接受通讯。TCP传输协议和NamedPipes协议本身就是双向的,因此在实现客户端回调时,你可以使用NetTcpBinding绑定和NetNamePipeBinding绑定。HTTP协议实现的模型不支持双向操作,因此你不能使用BasicHttpBinding绑定,WSHttpBinding绑定,或者WS2007HttpBinding绑定。这看起来在基于HTTP传输协议的在内网系统中有很大的缺陷。然而,为此WCF提供了WSDualHttpBinding绑定以应对这种缺陷。该绑定建立两个HTTP通道(一个用于客户端向服务发送请求;另外一个用于服务向客户端发送那个请求),当然该绑定隐藏了实现的细节,因此不只需要把它当作一个普通的双向绑定即可。
在WSDualHttpBinding绑定和WSHttpBinding绑定与WS2007HttpBinding绑定之间有一些非常重要的差别。尤其是WSDualHttpBinding绑定不支持传输级安全,但是它还是实现了可靠的会话(而且你不能禁用该特性)
 
 
posted @ 2011-11-03 09:29  On the road....  阅读(2028)  评论(7编辑  收藏  举报