[老老实实学WCF] 第十篇 消息通信模式(下) 双工
老老实实学WCF
第十篇 消息通信模式(下) 双工
在前一篇的学习中,我们了解了单向和请求/应答这两种消息通信模式。我们知道可以通过配置操作协定的IsOneWay属性来改变模式。在这一篇中我们来研究双工这种消息通信模式。
在一定程度上说,双工模式并不是与前面两种模式相提并论的模式,双工模式的配置方法同前两者不同,而且双工模式也是基于前面两种模式之上的。
在双工模式下,服务端和客户端都可以独立地调用对方,谁都不用等待谁的答复,同样也不期待对方答复,因为如果期待答复,就变成请求/应答模式了。也 就是说双方的调用都是单向调用,即我调你了,你不用回复我,你什么时候想回复我的时候呢你再调我,我就知道了,我是不会等着你的回复的。这样调用双方就会 有很好的异步体验,我想调的时候就调,然后我就去干别的,什么时候调用完成了,你可以通过回调来通知我,我再决定下一步的动作,谁都不等谁。
因为在服务模型中,调用总是由客户端首先发起的,所以一般说的调用,都是客户端的行为,在双工模式下,服务器也可以调用客户端,我们就把它叫做回 调,实际上这个调用和客户端的调用没什么本质区别,在回调的时候,服务端变成了客户端,客户端相当于在提供服务了,只不过总是客户端调用在前,我们就把服 务端对客户端的调用叫做回调了。
1. 建立双工通信的条件
要建立双工通信,必须使用支持双工通信的绑定,比如wsHttpBinding是不支持的,必须采用wsDualHttpBinding才行,他会建立两条绑定来实现互相调用,因此我们首先要注意的是选择正确的绑定。
要建立双工通信,必须使用会话,即将SeviceContract的SessionMode配置为SessionMode.Required。
2. 如何配置双工通信
为了更好地理解这个问题,我们先考虑单工的情形,在单工模式下(单向和请求应答),调用总是由客户端发起的,服务端可以回应也 可以不回应。我们是怎么配置实现这个的呢?我们首先让两端共享服务协定接口,这样客户端才知道怎样调用服务端,然后把服务实现类写在服务端,这样服务端才 能在收到请求的时候实例化这个类并执行服务的操作逻辑。也就是说,客户端要想调服务端,客户端必须拥有服务协定接口,而服务端必须拥有服务协定接口以及实 现接口的服务类,在运行时服务端还要有服务类的实例。这些是必备条件。
再说回双工,双工让服务端可以调客户端,那么道理同单工,必备条件也要有才行,只是他们的地位互换了。也就是说服务端必须拥有 协定接口,客户端必须拥有协定接口以及实现接口的类,在运行时客户端还要有类的实例。一般情况下我们管服务端的定义的协定接口叫做服务协定,协定接口实现 类叫做服务类,这回换到客户端,我们管它叫回调接口,回调类,其实作用是一样的。
也就是说在定义上,我们需要在两边都定义两个协定接口,一个服务协定接口,一个回调协定接口,把服务协定接口的实现类写在服务端,把回调协定接口的实现类写在客户端。
通过配置服务协定的ServiceContract的CallBackContract属性来指定回调的时候使用的回调协定,考虑下面的代码:
- [ServiceContract(SessionMode = SessionMode.Required,
- CallbackContract = typeof(IHelloWCFCallback))]
- public interface IHelloWCF
- {
- [OperationContract(IsOneWay = true)]
- void HelloWCF();
- }
- public interface IHelloWCFCallback
- {
- [OperationContract(IsOneWay = true)]
- void Callback(string msg);
- }
我们定义了一个IHelloWCFCallback的回调协定接口,这个接口的实现是写在客户端的,同时指定了IHelloWCF服务协定接口的回调协定为该回调协定接口,这样服务协定就知道怎样进行回调了。
同样在客户端要写出回调协定接口及其实现,如果我们使用了svcutil.exe或添加服务引用,系统会为我们自动填写回调协 定接口,但是不会为我们写实现类,毕竟她不知道我们的客户端在接受回调的时候执行怎样的逻辑。我们得自己编写,一个回调协定接口的实现类看上去是这样的:
- public class HelloWCFCallback : Services.IHelloWCFCallback
- {
- public void Callback(string msg)
- {
- Console.WriteLine(msg);
- }
- }
其实和实现服务协定没什么不同。
前面提过绑定需要支持双工,因此我们需要选择一个支持双工的绑定,我们采用wsDualHttpBinding。
- <endpoint address="" binding="wsDualHttpBinding" contract="LearnWCF.IHelloWCF"/>
以上双工通信的配置就完成了,我们还需要添加一些代码来对双工的运行时进行实现。
3. 双工的运行时实现
现在准备工作已经完成,那么在运行时需要什么呢?需要通道和实例。回想单工通信,我们通过代理类建立了一个基于服务协定的通道
到服务端,然后我们在这个通道上调用服务协定方法,在调用方法的时候,服务端实例上下文会生成并未我们new一个服务类的实例帮我们执行操作,当然这一步
是服务端的系统自动完成的,我们不需要做特别的配置,直接调用就行了。现在反过来服务端要调客户端了,服务端可没有代理类,那么通道何来呢?客户端这边也没有实例上下文和服务类实例,这边只是一个简单的控制台应用程序,我们怎么能指望客户端为我们自动生成这些呢?
所以我们得自己来。
首先在客户端我们要自己先实例化一个服务类的实例,然后用这个实例作为参数去创建一个实例上下文实例,这样运行时的服务实例就
有了,然后把这个实例上下文对象作为参数传给代理类的构造函数来初始化代理类,在这里代理类帮了我们大忙,他会检测到服务端元数据中服务协定使用双工,他
就会为我们准备好双工通道,当然前提是他会跟我们要实例上下文对象。这样我们通过代理类调用服务操作,双工通道就会建立了。看下面的代码(这是在客户端的
Program.cs中):
- //建立回调服务对象
- HelloWCFCallback callbackObject = new HelloWCFCallback();
- //建立实例上下文对象
- InstanceContext clientContext = new InstanceContext(callbackObject);
- //用建立好的实例上下文对象初始化代理类对象
- Services.HelloWCFClient client = new Services.HelloWCFClient(clientContext);
接下来轮到服务端,服务端既然受到的是一个支持双工的连接,他就可以在利用操作上下文对象来得到和打开回调的通道,回调通道使用回调协定声明的(正如通道使用服务协定声明一样)。然后再回调通道上调用回调操作就可以了:
- string msg = "Hello From Service! Time" + DateTime.Now.ToLongTimeString();
- //获得回调通道
- IHelloWCFCallback callbackChannel = OperationContext.Current.GetCallbackChannel<IHelloWCFCallback>();
- //调用回调操作
- callbackChannel.Callback(msg);
这样,双工的运行时就实现了。如果你对上面提到的有些迷糊,赶紧翻回第四篇和第五篇温习一下有关通信的基础知识。
4. 双工通信实例
我们通过一个完整的例子来理解一下双工通信的过程。
我用IIS作为服务端宿主,客户端用一个控制台应用程序。我们来实现一个比较简单的双工通信,客户端先向服务端发起一个调用,然后去干别的,服务端等五秒后回调客户端的回调方法。
你可能对前几篇讲的知识印象模糊了,我们这次从头做一次。当然,温习一下前面几篇的内容是最好的。
(1) 建立SVC文件。
首先我们先建立IIS宿主,建立一个HelloWCFService.svc的文件保存在IIS应用程序的根路径下。我的IIS应用程序的路径是
- http://localhost/IISService
因此这个文件的地址就变成了:
- http://localhost/IISService/HelloWCFService.svc
这个文件的内容只有一行:
- <%@ServiceHost language=c# Debug="true" Service="LearnWCF.HelloWCFService"%>
这行指令表示这是个WCF服务,服务的实现类是LearnWCF.HelloWCFService,注意这里命名空间要写全,名字你可以随意起。
(2) 建立服务代码文件。
代码文件是名为HelloWCFService.cs的文件,其实名字可以随意起,但是要保存在IIS应用程序根目录下的App_Code目录下(或者Bin目录也可以)。
代码文件内容如下:
- using System;
- using System.ServiceModel;
- namespace LearnWCF
- {
- [ServiceContract(SessionMode = SessionMode.Required,
- CallbackContract = typeof(IHelloWCFCallback))]
- public interface IHelloWCF
- {
- [OperationContract(IsOneWay = true)]
- void HelloWCF();
- }
- public interface IHelloWCFCallback
- {
- [OperationContract(IsOneWay = true)]
- void Callback(string msg);
- }
- public class HelloWCFService : IHelloWCF
- {
- private int _Counter;
- public void HelloWCF()
- {
- System.Threading.Thread.Sleep(5000);
- string msg = "Hello From Service! Time" + DateTime.Now.ToLongTimeString();
- //获得回调通道
- IHelloWCFCallback callbackChannel = OperationContext.Current.GetCallbackChannel<IHelloWCFCallback>();
- //调用回调操作
- callbackChannel.Callback(msg);
- }
- }
- }
注意几个要点。服务协定IHelloWCF的ServiceContract属性的两个设置一个是SessionMode为
Required,表示必须使用会话,另一个是指定了回调协定。同时我们能看到,把回调协定接口也定义了进来。服务操作HellWCF在受到调用后先休眠
5秒钟,然后从操作上下文获得回调通道,然后调用通道上的回调操作,把字符串Hello From Service和调用时间传递给了客户端。(3) 编写配置文件。
编写Web.Config文件并保存在IIS应用程序的根目录下(跟svc文件放在一起)内容如下:
- <configuration>
- <system.serviceModel>
- <services>
- <service name="LearnWCF.HelloWCFService" behaviorConfiguration="metadataExchange">
- <endpoint address="" binding="wsDualHttpBinding" contract="LearnWCF.IHelloWCF"/>
- <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
- </service>
- </services>
- <behaviors>
- <serviceBehaviors>
- <behavior name="metadataExchange">
- <serviceMetadata httpGetEnabled="true" />
- </behavior>
- </serviceBehaviors>
- </behaviors>
- </system.serviceModel>
- </configuration>
注意看绑定,配置成了支持双工的wsDualHttpBinding。
服务端的部分就写好了。
(4) 建立客户端应用程序
建立一个控制台应用程序,命名为ConsoleClient。
(5) 添加服务引用
在引用上右击,选择添加服务引用,并在地址中输入,并点击前往
- http://localhost/IISService/HelloWCFService.svc
在下面的命名空间中为代理类指定一个新的命名空间Services。点确定
此时系统为我们自动添加了App.Config和代理类文件,点击解决方案浏览器上方的查看所有文件,逐层展开服务引用,最后打开reference.cs看看有什么变化
以下的代码是系统生成的代理类代码,不是我们输入的
- //------------------------------------------------------------------------------
- // <auto-generated>
- // 此代码由工具生成。
- // 运行时版本:4.0.30319.261
- //
- // 对此文件的更改可能会导致不正确的行为,并且如果
- // 重新生成代码,这些更改将会丢失。
- // </auto-generated>
- //------------------------------------------------------------------------------
- namespace ConsoleClient.Services {
- [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
- [System.ServiceModel.ServiceContractAttribute(ConfigurationName="Services.IHelloWCF", CallbackContract=typeof(ConsoleClient.Services.IHelloWCFCallback), SessionMode=System.ServiceModel.SessionMode.Required)]
- public interface IHelloWCF {
- [System.ServiceModel.OperationContractAttribute(IsOneWay=true, Action="http://tempuri.org/IHelloWCF/HelloWCF")]
- void HelloWCF();
- }
- [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
- public interface IHelloWCFCallback {
- [System.ServiceModel.OperationContractAttribute(IsOneWay=true, Action="http://tempuri.org/IHelloWCF/Callback")]
- void Callback(string msg);
- }
- [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
- public interface IHelloWCFChannel : ConsoleClient.Services.IHelloWCF, System.ServiceModel.IClientChannel {
- }
- [System.Diagnostics.DebuggerStepThroughAttribute()]
- [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
- public partial class HelloWCFClient : System.ServiceModel.DuplexClientBase<ConsoleClient.Services.IHelloWCF>, ConsoleClient.Services.IHelloWCF {
- public HelloWCFClient(System.ServiceModel.InstanceContext callbackInstance) :
- base(callbackInstance) {
- }
- public HelloWCFClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName) :
- base(callbackInstance, endpointConfigurationName) {
- }
- public HelloWCFClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) :
- base(callbackInstance, endpointConfigurationName, remoteAddress) {
- }
- public HelloWCFClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
- base(callbackInstance, endpointConfigurationName, remoteAddress) {
- }
- public HelloWCFClient(System.ServiceModel.InstanceContext callbackInstance, System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
- base(callbackInstance, binding, remoteAddress) {
- }
- public void HelloWCF() {
- base.Channel.HelloWCF();
- }
- }
- }
我们可以看到,代理类为我们自动生成了回调协定IHelloWCFCallback,而且代理类HelloWCFClient的这些构造函数也不一样了,
需要我们提供InstanceContext实例了,而且继承的类也不再是ClientBase<>,而是
DuplexClientBase<>了,这就是代理发现我们用双工通信了,所以改变了代理类所继承的类为支持双工通信的基类。好,关掉它,我们继续完善客户端
(6) 编写客户端代码
我们还没有在客户端实现回调协定,首先要实现他,还要为运行时添加调用代码,Program.cs的代码如下:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.ServiceModel;
- namespace ConsoleClient
- {
- class Program
- {
- static void Main(string[] args)
- {
- //建立回调服务对象
- HelloWCFCallback callbackObject = new HelloWCFCallback();
- //建立实例上下文对象
- InstanceContext clientContext = new InstanceContext(callbackObject);
- //用建立好的实例上下文对象初始化代理类对象
- Services.HelloWCFClient client = new Services.HelloWCFClient(clientContext);
- Console.WriteLine("Client Call Begin:"+DateTime.Now.ToLongTimeString());
- client.HelloWCF();
- Console.WriteLine("Client Call End:"+DateTime.Now.ToLongTimeString());
- Console.WriteLine("Client can process other things");
- Console.ReadLine();
- }
- }
- public class HelloWCFCallback : Services.IHelloWCFCallback
- {
- public void Callback(string msg)
- {
- Console.WriteLine(msg);
- }
- }
- }
首先在下面我们实现了回调协定接口,逻辑就是把服务端传过来的参数输出。
然后我们建立服务类的对象以及实例上下文对象,接着构造代理类对象。
接下来就是调用服务操作了。我们在这里记录了时间便于观察顺序。注意看,这里客户端没有等待服务端的任何返回,也没有任何的输出的动作。
完成 F5 运行以下,结果是这样的:
我们参照源代码可以看出,客户端就调用了服务端一次,耗时2秒,然后客户端就可以去做别的了。在客户端发起调用5秒以后,服务端向客户端执行了一次调用,也就是客户端调用的服务操作休眠了5秒以后执行了回调。最后一条输出,是服务端主动调用的客户端的回调服务方法输出的。
5. 总结
双工通信有点小复杂,需要反复琢磨,在思考的时候有的小窍门,就是把单工通信需要的条件和过程仔细想明白,然后依样反方向复制一份,缺什么补什么就是双工通信了。
一些关键点,可以反复思考:
(1) 通信两端都有两个协定接口,接口的实现在两边一边一个。
(2) 支持双工的绑定。
(3) 指定服务协定的回调协定以建立回调联系。
(4) 客户端自己构造运行时服务类对象和实例上下文对象。
(5) 服务端通过操作上下文获得回调通道 。
(6) 需要会话的支持。
(7) 两个协定中的协定操作应为单向模式。
[老老实实学WCF] 第十篇 消息通信模式(下) 双工的更多相关文章
- wcf_消息通信模式(下) 双工通讯
原文:[老老实实学WCF] 第十篇 消息通信模式(下) 双工 第十篇 消息通信模式(下) 双工 在前一篇的学习中,我们了解了单向和请求/应答这两种消息通信模式.我们知道可以通过配置操作协定的IsOne ...
- [老老实实学WCF] 第四篇 初探通信--ChannelFactory
老老实实学WCF 第四篇 初探通信--ChannelFactory 通过前几篇的学习,我们简单了解了WCF的服务端-客户端模型,可以建立一个简单的WCF通信程序,并且可以把我们的服务寄宿在IIS中了. ...
- (转)[老老实实学WCF] 第四篇 初探通信--ChannelFactory
第四篇 初探通信--ChannelFactory 通过前几篇的学习,我们简单了解了WCF的服务端-客户端模型,可以建立一个简单的WCF通信程序,并且可以把我们的服务寄宿在IIS中了.我们不禁感叹WCF ...
- [老老实实学WCF] 第九篇 消息通信模式(上) 请求应答与单向
老老实实学WCF 第九篇 消息通信模式(上) 请求应答与单向 通过前两篇的学习,我们了解了服务模型的一些特性如会话和实例化,今天我们来进一步学习服务模型的另一个重要特性:消息通信模式. WCF的服务端 ...
- [老老实实学WCF] 第五篇 再探通信--ClientBase
老老实实学WCF 第五篇 再探通信--ClientBase 在上一篇中,我们抛开了服务引用和元数据交换,在客户端中手动添加了元数据代码,并利用通道工厂ChannelFactory<>类创 ...
- [老老实实学WCF] 第七篇 会话
老老实实学WCF 第七篇 会话 通过前几篇的学习,我们已经掌握了WCF的最基本的编程模型,我们已经可以写出完整的通信了.从这篇开始我们要深入地了解这个模型的高级特性,这些特性用来保证我们的程序运行的高 ...
- [老老实实学WCF] 第八篇 实例化
老老实实学WCF 第八篇 实例化 通过上一篇的学习,我们简单地了解了会话,我们知道服务端和客户端之间可以建立会话连接,也可以建立非会话连接,通信的绑定和服务协定的 ServiceContract 的S ...
- [老老实实学WCF] 第六篇 元数据交换
老老实实学WCF 第六篇 元数据交换 通过前两篇的学习,我们了解了WCF通信的一些基本原理,我们知道,WCF服务端和客户端通过共享元数据(包括服务协定.服务器终结点信息)在两个 终结点上建立通道从而进 ...
- [老老实实学WCF] 第三篇 在IIS中寄存服务
老老实实学WCF 第三篇 在IIS中寄宿服务 通过前两篇的学习,我们了解了如何搭建一个最简单的WCF通信模型,包括定义和实现服务协定.配置服务.寄宿服务.通过添加服务引用的方式配置客户端并访问服务.我 ...
随机推荐
- Codeforces Educational Codeforces Round 3 B. The Best Gift 水题
B. The Best Gift 题目连接: http://www.codeforces.com/contest/609/problem/B Description Emily's birthday ...
- Effective C++笔记04:设计与声明
条款18:让接口easy被正确使用,不易被误用 1,好的接口非常easy被正确使用,不easy被误用.你应该在你的全部接口中努力达成这些性质. 2,"促进正使用"的办法包含接口的一 ...
- js 处理url中文参数 java端接收处理
正常情况下当http请求中带有中文参数时,浏览器会自动对中文进行一次编码(按照当前页面的pageEncoding),java端容器会对接收到的参数自动进行一次转码,则request.getParame ...
- 分布式缓存技术redis学习(一)——redis简介以及linux上的安装
redis简介 redis是NoSQL(No Only SQL,非关系型数据库)的一种,NoSQL是以Key-Value的形式存储数据.当前主流的分布式缓存技术有redis,memcached,ssd ...
- SQL性能优化十条经验
1.查询的模糊匹配 尽量避免在一个复杂查询里面使用 LIKE '%parm1%'—— 红色标识位置的百分号会导致相关列的索引无法使用,最好不要用. 解决办法: 其实只需要对该脚本略做改进,查询速度便会 ...
- EMS电子面单接口对接使用-免费版
快递鸟电子面单接口,可一次对接15家快递公司, 无需和每一家快递公司做对接.支持快递有四通一达.顺丰.EMS.宅急送.德邦.优速等15家快递公司,对顺丰有电子面单服务需求的可以选择顺丰自有的电子面单或 ...
- itextsharp去掉PDF加密
在操作PDF文件时会遇到PDF文件加密了,不能操作的问题,从网络中查找资料一上午,鼓捣出如下的代码,可实现将已加密的PDF转化成未加密的PDF文件,纯代码,无需借助PDF解密软件,使用前需要导入如下引 ...
- 关键字提取算法之TF-IDF扫盲
TF-IDF(term frequency–inverse document frequency)是一种用于资讯检索与资讯探勘的常用加权技术.TF-IDF是一种统计方法,用以评估一字词对于一个文件集或 ...
- hadoop群集安装中碰到的问题
在hadoop群集安装结束后,进行格式测试出现问题如下 格式化 cd /data/hadoop/bin ./hdfs namenode -format 15/01/21 05:21:17 WARN f ...
- 【阿里云产品公测】在Laravel4框架中使用阿里云OCS缓存
作者:阿里云用户 supechina Laravel 是我最近用得非常多而且越用就越喜欢的一款PHP框架,由于没有向下兼容的历史包袱,完全面向对象的风格,借助 Facades 优雅的 IoC Cont ...