[老老实实学WCF] 第十篇 消息通信模式(下) 双工

原创 2012年03月30日 07:35:31

老老实实学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是.NET提供的一种服务,可以将自己写的程序(完成特定功能,比如从数据库中读取数据操作等)分装成服务以后,发布到服务器上。然后会生成一个网址,客户端在编程的时候,可以引用这个服务,使用这个服务中...
  • fynjy
  • fynjy
  • 2015-07-14 10:50
  • 3423

[老老实实学WCF] 第四篇 初探通信--ChannelFactory

老老实实学WCF 第四篇 初探通信--ChannelFactory   通过前几篇的学习,我们简单了解了WCF的服务端-客户端模型,可以建立一个简单的WCF通信程序,并且可以把我们的服务寄宿在I...

[老老实实学WCF] 第五篇 再探通信--ClientBase

老老实实学WCF 第五篇 再探通信--ClientBase   在上一篇中,我们抛开了服务引用和元数据交换,在客户端中手动添加了元数据代码,并利用通道工厂ChannelFactory来完成同样的...

[老老实实学WCF] 第三篇 在IIS中寄存服务

老老实实学WCF 第三篇 在IIS中寄宿服务   通过前两篇的学习,我们了解了如何搭建一个最简单的WCF通信模型,包括定义和实现服务协定、配置服务、寄宿服务、通过添加服务引用的方式配置客户端并访...

WCF并发模型

(原创:灰灰虫的家http://hi.baidu.com/grayworm) 当多个线程同时访问相同的资源的时候就会产生并发,WCF缺省情况下会保护并发访问。 对并发访问需要恰当处理,控制不好不仅...

【Visual C++】游戏开发笔记十六 讲解一个完整的回合制游戏demo

本系列文章由zhmxy555编写,转载请注明出处。文章链接  http://blog.csdn.net/zhmxy555/article/details/7447864作者:毛星云    邮箱: ha...

WCF 学习总结1 -- 简单实例

从VS2005推出WCF以来,WCF逐步取代了Remoting, WebService成为.NET上分布式程序的主要技术。WCF统一的模型整合了以往的 WebService、Remoting、MSMQ...

传说中的WCF(9):流与文件传输

在使用Socket/TCP来传输文件,弄起来不仅会有些复杂,而且较经典的“粘包”问题有时候会让人火冒七丈。如果你不喜欢用Socket来传文件,不妨试试WCF,WCF的流模式传输还是相当强大和相当实用的...

我在ESB上走的弯路

这些年人们一直在谈论SOA,有过面向服务开发经验的人对企业服务总线ESB一定不会陌生。但大家对ESB的理解却并不相同,可能是千奇百怪的,我找了很多资料,也没有找到一个能被大家普遍接受的定义。正式因为对...

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)

本篇文章里,我们首先对Direct3D之中固定功能流水线中的纹理映射相关的知识进行了详尽的剖析,然后我们将DirectInput这套游戏输入控制领域一手遮天的API用一个C++类进行了抽象和封装,这样...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)