WCF TCP双工模式详解介绍

在一个基于面向服务的分布式环境中,借助一个标准的、平台无关的通信协议,使各个服务通过SOAP Message实现相互之间的交互。这个交互的过程实际上就是信息交换的过程。WCF支持不同形式的信息交换,我们把这称之为信息交换模式(Message Exchange Pattern(简称MEP),下同), 常见的MEP包括: 请求/答复,单向模式和双工模式。通过采用双工的MEP,我们可以实现在服务端调用客户端的操作。虽然WCF为我们实现底层的通信细节,使得我们把精力转移到业务逻辑的实现,进行与通信协议无关的编程,但是对通信协议的理解有利于我们根据所处的具体环境选择一个合适的通信协议。说到通信协议, WCF 经常使用的是以下4个:Http,TCP,Named Pipe,MSMQ。

我们用上一文章( WCF学习之旅—HTTP双工模式(二十) )中的示例,进行一下修改,变成一个TCP双向通信的WCF服务应用程序。下面直接上代码。

1 . Contract

using Contracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace Contracts
{

    // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IBookService”。
    [ServiceContract(CallbackContract = typeof(ICallback))]
    public interface IBookService
    {

       /// <summary>
       /// 请求与答复模式,默认模式
       /// </summary>
       /// <param name="Id">书籍ID</param>
       /// <returns></returns>
        [OperationContract]
        string GetBook(string Id);
        /// <summary>

        /// 单工模式,显示名称
        /// </summary>
        /// <param name="name">书籍名称</param>
        [OperationContract(IsOneWay = true)]
        void ShowName(string name);
        /// <summary>
        /// 双工模式,显示名称
        /// </summary>
        /// <param name="name">书籍名称</param>
        [OperationContract(IsOneWay = true)]
        void DisplayName(string name);
 

    }

}

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace Contracts
{

    public interface ICallback
    {

        [OperationContract(IsOneWay = true)]
        void DisplayResult(string result);

    }
}
2 . WcfServiceLib

using Contracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
 

namespace WcfServiceLib
{

    // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码、svc 和配置文件中的类名“BookService”。

    // 注意: 为了启动 WCF 测试客户端以测试此服务,请在解决方案资源管理器中选择 BookService.svc 或 BookService.svc.cs,然后开始调试。
    public class BookService : IBookService
    {

        /// <summary>
        /// 请求与答复模式,默认模式
        /// </summary>
        /// <param name="Id">书籍ID</param>
        /// <returns></returns>
        public string GetBook(string Id)
        {

            System.Threading.Thread.Sleep(20000);
            int bookId = Convert.ToInt32(Id);
            Books book = SetBook(bookId);
            string xml = XMLHelper.ToXML<Books>(book);
            return xml;

        }

 

        public Books SetBook(int Id)
        {

            Books book = new Books();
            book.BookID = Id;
            book.AuthorID = 1;
            book.Category = "IBM";
            book.Price = 39.99M;
            book.Numberofcopies = 25;
            book.Name = "DB2数据库性能调整和优";
            book.PublishDate = new DateTime(2015, 2, 23);
            return book;
        }

        /// <summary>
        /// 单工模式,显示名称
        /// </summary>
        /// <param name="name">名称</param>
        public void ShowName(string name)
        {

            string result = string.Format("书籍名称:{0},日期时间{1}", name, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            Console.WriteLine("\r\n" + result);         

        }

        /// <summary>
        /// 双工模式,回调显示结果
        /// </summary>
        /// <param name="name">名称</param>
        public void DisplayName(string name)
        {
            string result=string.Format("书籍名称:{0},日期时间{1}", name, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            Console.WriteLine("\r\n" + result);
            ICallback call = OperationContext.Current.GetCallbackChannel<ICallback>();
            call.DisplayResult("回调客户端---"+result);

        }

    }

}
在服务端,通过OperationContext.Current.GetCallbackChannel来获得客户端指定的CallbackContext 实例,进而调用客户端的操作。

3 . Hosting :

宿主配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />

  </startup>
  <system.serviceModel>
    <diagnostics>
      <messageLogging logEntireMessage="true" logKnownPii="false" logMalformedMessages="true"
        logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" />
      <endToEndTracing propagateActivity="true" activityTracing="true"
        messageFlowTracing="true" />
    </diagnostics>
 

    <services>
      <service  name="WcfServiceLib.BookService">
        <endpoint address="net.tcp://127.0.0.1:9999/BookService" binding="netTcpBinding"
        contract="Contracts.IBookService" />

      </service>
    </services>
  </system.serviceModel>
</configuration>
我们通过netTcpBinding来模拟基于TCP的双向通信。代码如下:

using Contracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;
using WcfServiceLib;

 

namespace ConsoleHosting
{

    class Program
    {

        static void Main(string[] args)
        {
            Console.WriteLine("输入启动方式,C--Code A -- App.config方式!");
            string key = Console.ReadLine();
            switch (key)
            {
                case "C":
                    StartByCode();
                    break;
                case "A":
                    StartByConfig();
                    break;
                default:
                    Console.WriteLine("没有选择启动方式,使用默认方式");
                    StartByCode();
                    break;

            }
        }

        private static void StartByCode()
        {

            //创建宿主的基地址
            Uri baseAddress = new Uri("http://localhost:8080/BookService");
            //创建宿主
            using (ServiceHost host = new ServiceHost(typeof(BookService), baseAddress))
            {

                //向宿主中添加终结点
                host.AddServiceEndpoint(typeof(IBookService), new WSDualHttpBinding(), baseAddress);
                if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null)

                {
                    //将HttpGetEnabled属性设置为true
                    ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
                    behavior.HttpGetEnabled = true;
                    behavior.HttpGetUrl = baseAddress;
                    //将行为添加到Behaviors中

                    host.Description.Behaviors.Add(behavior);
                    //打开宿主

                    host.Opened += delegate
                    {
                        Console.WriteLine("BookService控制台程序寄宿已经启动,HTTP监听已启动....,按任意键终止服务!");
                    };

                    host.Open();
                    //print endpoint information                 

                    Console.ForegroundColor = ConsoleColor.Yellow;
                    foreach (ServiceEndpoint se in host.Description.Endpoints)
                    {
                        Console.WriteLine("[终结点]: {0}\r\n\t[A-地址]: {1} \r\n\t [B-绑定]: {2} \r\n\t [C-协定]: {3}",
                     se.Name, se.Address, se.Binding.Name, se.Contract.Name);
                    }
                    Console.Read();
                }
            }

        }

        private static void StartByConfig()
        {
            using (ServiceHost host = new ServiceHost(typeof(BookService)))
            {
                host.Opened += delegate
                {

                    Console.WriteLine("BookService控制台程序寄宿已经启动,TCP监听已启动....,按任意键终止服务!");
                };

                host.Open();
                //print endpoint information                

                Console.ForegroundColor = ConsoleColor.Yellow;
                foreach (ServiceEndpoint se in host.Description.Endpoints)
                {
                    Console.WriteLine("[终结点]: {0}\r\n\t[A-地址]: {1} \r\n\t [B-绑定]: {2} \r\n\t [C-协定]: {3}",
                 se.Name, se.Address, se.Binding.Name, se.Contract.Name);
 
                }

                Console.Read();
            }

        }

    }

}

 
4 .客户端:

配置文件中的信息进行修改:

  <system.serviceModel>
        <client>
           <endpoint address="net.tcp://localhost:9999/BookService" binding="netTcpBinding"

                 bindingConfiguration="" contract="Contracts.IBookService"

                 name="BookServiceEndpoint" />
        </client>
    </system.serviceModel>
接下来实现对双工服务的调用,下面是相关的配置和托管程序。在服务调用程序中,通过 DuplexChannelFactory<TChannel>创建服务代理对 象,DuplexChannelFactory<TChannel>和ChannelFactory<TChannel>的功能 都是一个服务代理对象的创建工厂,不过DuplexChannelFactory<TChannel>专门用于基于双工通信的服务代理的创 建。在创建DuplexChannelFactory<TChannel>之前,先创建回调对象,并通过InstanceContext对回 调对象进行包装。代码如下:

private void btnTcpDuplex_Click(object sender, EventArgs e)
        {

            DuplexChannelFactory<IBookService> channelFactory = new DuplexChannelFactory<IBookService>(instanceContext, "BookServiceEndpoint");
            IBookService client = channelFactory.CreateChannel();

                      

            //在BookCallBack对象的mainThread(委托)对象上搭载两个方法,在线程中调用mainThread对象时相当于调用了这两个方法。

            textBox1.Text += string.Format("开始调用wcf服务:{0}\r\n\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

            client.DisplayName("TCP---科学可以这样看丛书");

            textBox1.Text += string.Format("\r\n\r\n调用结束:{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        }
在创建 DuplexChannelFactory< IBookService >中,指定了Callback Context Instance: 一个实现了Callback Contract的BookCallBack对象。该对象在Service中通过 OperationContext.Current.GetCallbackChannel<ICallback>()获得。

通过运行程序之后的结果如下图:

 

  2. 基于 Http 的双向通讯 V.S. 基于 TCP 的双向通讯

由于Http和TCP在各自协议上的差异,他们实现双向通信的发式是不同的。

Http是一个应用层的协议,它的主要特征就是无连接和无状态。它采用传统的“请求/回复”的方式进行通信,客户端发送Http Request请求服务端的某个资源,服务端接收到该Http Request, 回发对应的Http Response。当客户端接收到对应的Response,该连接就会关闭。也就是说客户端和服务端的 连接仅仅维持在发送Request到接收到Response这一段时间内。同时,每次基于Http的 连接是相互独立,互不相干的,当前连接无法获得上一次连接的状态。为了保存调用的的状态信 息,ASP.NET通过把状态信息保存在服务端的Session之中,具体的做法是:ASP.NET为每个Session创建一个 Unique ID,与之关联一个HttpSessionState对象,并把状态信息保存在内存中或者持久的存储介质(比如SQL Server)中。而WCF则采用另外的方式实现对Session的支持:每个Session关联到某个Service Instance上。

我们来讲一下HTTP双向通信的过程,当客户端通过HTTP请求调用WCF服务之前,会有一个终结点在客户端被创建,用于监听服务端对它的Request。客户端对 WCF服务的调用会建立一个客户端到服务端的连接,当WCF服务在执行操作过程中需要回调对应的客户端,实际上会建立另一个服务端到客户端的Http 连接。虽然我们时候说WCF为支持双向通信提供一个双工通道,实际上这个双工通道是由两个HTTP连接组成的。

再来看一下TCP的双向通信的过程,对于TCP传输层协议,它则是一个基于连接的协议,在正式进行数据传输的之前,必须要在客户端和服务端之间建立一个连接,连接的建立通过经典的“3次握手”来实现。TCP天生就具有双工的特性,也就是说当连接 被创建之后,从客户端到服务端,和从服务端到客户端的数据传递都可以利用同一个连接来实现。对于WCF中的双向通信,客户端调用服务端,服务端回调客户端的操作使用的都是同一个连接、同一个通道。所以基于TCP的双工通信模式才是真正意义上的双工通信模式。

时间: 2024-10-23 07:01:52

WCF TCP双工模式详解介绍的相关文章

HTTP协议Keep-Alive模式详解

HTTP协议Keep-Alive模式详解     1.什么是Keep-Alive模式    我们知道HTTP协议采用"请求-应答"模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HTTP协议为无连接的协议):当使用Keep-Alive模式(又称持久连接.连接重用)时,Keep-Alive功能使客户端到服 务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接.     

Javascript的严格模式详解

一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict mode).顾名思义,这种模式使得Javascript在更严格的条件下运行. 设立"严格模式"的目的,主要有以下几个: - 消除Javascript语法的一些不合理.不严谨之处,减少一些怪异行为; - 消除代码运行的一些不安全之处,保证代码运行的安全: - 提高编译器效率,增加运行速度: - 为未来新版本的Javascript做好铺垫. "严格模式&quo

深入理解JavaScript系列(26):设计模式之构造函数模式详解

 这篇文章主要介绍了深入理解JavaScript系列(26):设计模式之构造函数模式详解,本文讲解了基本用法.构造函数与原型.只能用new吗?.强制使用new.原始包装函数等内容,需要的朋友可以参考下     介绍 构造函数大家都很熟悉了,不过如果你是新手,还是有必要来了解一下什么叫构造函数的.构造函数用于创建特定类型的对象--不仅声明了使用的对象,构造函数还可以接受参数以便第一次创建对象的时候设置对象的成员值.你可以自定义自己的构造函数,然后在里面声明自定义类型对象的属性或方法. 基本用法 在

深入理解JavaScript系列(27):设计模式之建造者模式详解

 这篇文章主要介绍了深入理解JavaScript系列(27):设计模式之建造者模式详解,建造者模式可以将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示,需要的朋友可以参考下     介绍 在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成:由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法确相对稳定.如何应对这种变化?如何提供一种"封装机制"来隔离出"复

深入理解JavaScript系列(28):设计模式之工厂模式详解

 这篇文章主要介绍了深入理解JavaScript系列(28):设计模式之工厂模式详解,工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类,需要的朋友可以参考下     介绍 与创建型模式类似,工厂模式创建对象(视为工厂里的产品)时无需指定创建对象的具体类. 工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类.该模式使一个类的实例化延迟到了子类.而子类可以重写接口方法以便创建的时候指定自己的对象类型. 这个模式十分有用,尤其是创建对象的流程赋值的时候,比如依赖于

深入理解JavaScript系列(29):设计模式之装饰者模式详解

 这篇文章主要介绍了深入理解JavaScript系列(29):设计模式之装饰者模式详解,装饰者用用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用(例如装饰者的构造函数),需要的朋友可以参考下     介绍 装饰者提供比继承更有弹性的替代方案. 装饰者用用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用(例如装饰者的构造函数). 装饰者用于通过重载方法的形式添加新功能,该模式可以在被装饰者前面或者后面加上自己的行为以达到特定的目的.

深入理解JavaScript系列(30):设计模式之外观模式详解

 这篇文章主要介绍了深入理解JavaScript系列(30):设计模式之外观模式详解,外观模式(Facade)为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口值得这一子系统更加容易使用,需要的朋友可以参考下     介绍 外观模式(Facade)为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口值得这一子系统更加容易使用. 正文 外观模式不仅简化类中的接口,而且对接口与调用者也进行了解耦.外观模式经常被认为开发者必备,它可以将一些复杂操作封装

深入理解JavaScript系列(31):设计模式之代理模式详解

 这篇文章主要介绍了深入理解JavaScript系列(31):设计模式之代理模式详解,代理模式使得代理对象控制具体对象的引用,代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西,需要的朋友可以参考下     介绍 代理,顾名思义就是帮助别人做事,GoF对代理模式的定义如下: 代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问. 代理模式使得代理对象控制具体对象的引用.代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西. 正文 我们

深入理解JavaScript系列(33):设计模式之策略模式详解

 这篇文章主要介绍了深入理解JavaScript系列(33):设计模式之策略模式详解,策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户,需要的朋友可以参考下     介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很多时候都是按照swith语句来判断,但是这就带来几个问题,首先如果增加需求的话