WCF初探-5:WCF消息交换模式之双工通讯(Duplex)

双工通讯Duplex具有以下特点:

1它可以在处理完请求之后,通过请求客户端中的回调进行响应操作

2.消息交换过程中,服务端和客户端角色会发生调换

3.服务端处理完请求后,返回给客户端的不是reply,而是callback请求。

4.Duplex模式对Bindding有特殊的要求,它要求支持Duplex MEP(Message Exchange Pattern),如WSDualHttpBinding和NetTcpBinding

注意:在WCF预定义绑定类型中,WSDualHttpBinding和NetTcpBinding均提供了对双工通信的支持,但是两者在对双工通信的实现机制上却有本质的区别。WSDualHttpBinding是基于HTTP传输协议的;而HTTP协议本身是基于请求-回复的传输协议,基于HTTP的通道本质上都是单向的。WSDualHttpBinding实际上创建了两个通道,一个用于客户端向服务端的通信,而另一个则用于服务端到客户端的通信,从而间接地提供了双工通信的实现。而NetTcpBinding完全基于支持双工通信的TCP协议。

我今天的实例讲的就是双工通讯的一个使用场景订阅-发布模式,此时消息的双方变成了订阅者和发布者。订阅者有两个操作(订阅消息、取消订阅),当订阅者订阅消息后,发布者就开始向订阅者广播消息,当订阅者取消订阅后,就不会接收到广播的消息。具体如下图所示:

接下来我们我们创建基于WCF的双工通讯的订阅与发布模式的服务。工程结构如下图所示:

Publisher(发布者)和Subscriber(订阅者)都是Winform工程,我们把发布者作为服务端,订阅者作为客户端,发布者还需要承载寄宿服务。如下图设置好发布者和订阅者的界面,

发布者有一个寄宿服务的lable显示服务是否寄宿成功,一个消息文本框和一个发布按钮,输入文本后,点击发布就可以向订阅的客户端广播消息。

订阅者的界面上有一个消息接收的listbox,以及订阅消息和取消订阅按钮,还有一个输入客户端名称的文本框,界面如下图所示:

接下来我们开始实际的代码操作,首先完成发布者(服务端)的代码实现,创建IPublisher.cs文件,定义服务接口和回调接口,代码如下:

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

namespace Publisher
{

     [ServiceContract(CallbackContract = typeof(IPublisherEvents))]
     public interface IPublisher
     {
         [OperationContract(IsOneWay = true)]
         void Subscriber(string clientID,string clientName);               //订阅消息

         [OperationContract(IsOneWay = true)]
         void UnSubscriber(string clientID, string clientName);            //取消订阅
     }

     public interface IPublisherEvents
     {
         [OperationContract(IsOneWay = true)]
         void PublishMessage(string message);                        //发布消息
     }
}

接口里面只定义了订阅者(客户端)调用的订阅消息和取消订阅的方法,以及服务端调用客户端的回调方法PublishMessage,然后我们在FormPublisher.cs里面实现该接口,具体代码如下:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.ServiceModel;
using System.Threading;

namespace Publisher
{

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public partial class FormPublisher : Form, IPublisher, IDisposable
    {
        //定义回调客户端集合
        public static List<IPublisherEvents> ClientCallbackList { get; set; }

        public FormPublisher()
        {
            InitializeComponent();
            ClientCallbackList = new List<IPublisherEvents>();
        }

        //寄宿服务
        private ServiceHost _host = null;
        private void FormPublisher_Load(object sender, EventArgs e)
        {
            _host = new ServiceHost(typeof(Publisher.FormPublisher));
            _host.Open();
            this.label1.Text = "MessageService Opened.";
        }

        //关闭窗体
        private void FormPublisher_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_host != null)
            {
                _host.Close();
                IDisposable host = _host as IDisposable;
                host.Dispose();
            }
        }

        //发布消息
        private void btn_Publish_Click(object sender, EventArgs e)
        {

            var list =Publisher.FormPublisher.ClientCallbackList;
            if (list == null || list.Count == 0)
                return;
            lock (list)
            {
                foreach (var client in list)
                {
                    client.PublishMessage(this.txt_Message.Text);
                }
            }

        }

        //实现订阅
        public void Subscriber(string clientID, string clientName)
        {
            var client = OperationContext.Current.GetCallbackChannel<IPublisherEvents>();
            var sessionid = OperationContext.Current.SessionId;
            MessageBox.Show( string.Format("客户端{0} 开始订阅消息。", clientName));
            OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
            ClientCallbackList.Add(client);

        }

        //取消订阅
        public void UnSubscriber(string clientID, string clientName)
        {
            var client = OperationContext.Current.GetCallbackChannel<IPublisherEvents>();
            var sessionid = OperationContext.Current.SessionId;
            MessageBox.Show(string.Format("客户端{0}取消订阅消息", clientName));
            OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
            ClientCallbackList.Remove(client);
        }

        //关闭通道,移除回调客户端
        void Channel_Closing(object sender, EventArgs e)
        {
            lock (ClientCallbackList)
            {
                ClientCallbackList.Remove((IPublisherEvents)sender);
            }
        }

    }
}

注意:当前使用了实例上下文模式为单例模式,我们启用的是同一个实例上下文模式,即客户端共享同一个同一个会话,关于实例模式有三种:

1. Single —— 表示所有的客户端共享一个会话(服务对象)(服务关闭时才会销毁服务对象)

2. PerCall —— 表示每次调用都会创建一个会话(服务对象)(调用完毕后就会销毁服务对象)

3. PerSession —— 表示为每个连接(每个客户端代理对象) 创建一个会话(服务对象),只有指定IsTerminating=true的操作被调用,或者是设定的SessionTimeout超时的时候,服务对象会被销毁。但支持Session的Binding只有:WSHttpBinding、WSDualHttpBinding、WSFederationHttpBinding、NetTcpBinding。

关于实例上下文模式,我将在后期博文中详细介绍。

完成后,我们就开始配置我们的服务端的”ABC”,服务端的配置文件如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true"/>
  </system.web>
  <system.serviceModel>

    <services>
      <service name="Publisher.FormPublisher">
        <endpoint address="" binding="netTcpBinding" bindingConfiguration="netTcpExpenseService_ForSupplier" contract="Publisher.IPublisher">
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://172.0.0.1:9999/WcfDuplexService/"/>
            <add baseAddress="http://172.0.0.1:9998/WcfDuplexService"/>
          </baseAddresses>
        </host>
      </service>
    </services>

    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="False"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <bindings>
      <netTcpBinding>
        <binding name="netTcpExpenseService_ForSupplier" closeTimeout="00:01:00"
            openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
            transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
            hostNameComparisonMode="StrongWildcard" listenBacklog="10"
            maxBufferPoolSize="2147483647" maxBufferSize="2147483647" maxConnections="10"
            maxReceivedMessageSize="2147483647">
          <readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="2147483647"
              maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <reliableSession ordered="true" inactivityTimeout="00:10:00"
              enabled="false" />
          <security mode="None">
            <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
            <message clientCredentialType="Windows" />
          </security>
        </binding>
      </netTcpBinding>
    </bindings>

  </system.serviceModel>

</configuration>

到此我们的发布者(服务端)的代码完成,编译后启动我们的Publisher.exe就可以看到服务寄宿成功的界面如下图所示:

接下来我们在Subscriber项目中添加服务引用,如下图所示:

注意:我们选择http://172.0.0.1:9998/WcfDuplexService地址,因为我们的服务已经采用了元数据地址发布

接下来我们实现FormSubscriber.cs窗体的代码:

using System;
using System.Windows.Forms;
using System.ServiceModel;
using System.Threading;
using Subscriber.WcfDuplexService;

namespace Subscriber
{
    public partial class FormSubscriber : Form, IPublisherCallback
    {
        PublisherClient proxy = null;

        public FormSubscriber()
        {
             InitializeComponent();
             InstanceContext instance = new InstanceContext(this);
             proxy = new PublisherClient(instance);

             btn_cancle.Enabled = false;
        }

        //实现客户端回调函数
        public void PublishMessage(string message)
        {
            string msg = string.Format("来自服务端的广播消息 : {0}",message);
            lst_getMsg.Items.Add(msg);
         }

        //订阅消息
        private void btn_ok_Click(object sender, EventArgs e)
        {
            btn_ok.Enabled = false;
            btn_cancle.Enabled = true;

            string ClientID = System.Guid.NewGuid().ToString();
            string ClientName = this.textBox1.Text;
            proxy.Subscriber(ClientID, ClientName);
        }

        //取消订阅
        private void btn_cancle_Click(object sender, EventArgs e)
        {
            btn_ok.Enabled = true;
            btn_cancle.Enabled = false;

            string ClientID = System.Guid.NewGuid().ToString();
            string ClientName = this.textBox1.Text;
            proxy.UnSubscriber(ClientID, ClientName);
        }
    }
}

到此,我们整个解决方案已经完成,接下来,我们运行程序来验证我们需要的结果,首先启动发布者(即服务端),再启动订阅者(即客户端,注意:这里我们启动两个,方便验证程序效果),运行效果如下:

效果1:client1和client2都订阅消息,此时两个客户端都能收到广播的消息

效果2:client1订阅消息和client2取消订阅,此时只有client1能收到广播的消息

效果3:client1取消订阅和client2订阅消息,此时只有client2收到广播的消息

时间: 2024-10-08 12:06:38

WCF初探-5:WCF消息交换模式之双工通讯(Duplex)的相关文章

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

原文:[老老实实学WCF] 第十篇 消息通信模式(下) 双工 老老实实学WCF 第十篇 消息通信模式(下) 双工 在前一篇的学习中,我们了解了单向和请求/应答这两种消息通信模式.我们知道可以通过配置操作协定的IsOneWay属性来改变模式.在这一篇中我们来研究双工这种消息通信模式. 在一定程度上说,双工模式并不是与前面两种模式相提并论的模式,双工模式的配置方法同前两者不同,而且双工模式也是基于前面两种模式之上的. 在双工模式下,服务端和客户端都可以独立地调用对方,谁都不用等待谁的答复,同样也不期

wcf_消息通信模式(下) 双工通讯

原文:[老老实实学WCF] 第十篇 消息通信模式(下) 双工 第十篇 消息通信模式(下) 双工 在前一篇的学习中,我们了解了单向和请求/应答这两种消息通信模式.我们知道可以通过配置操作协定的IsOneWay属性来改变模式.在这一篇中我们来研究双工这种消息通信模式. 在一定程度上说,双工模式并不是与前面两种模式相提并论的模式,双工模式的配置方法同前两者不同,而且双工模式也是基于前面两种模式之上的. 在双工模式下,服务端和客户端都可以独立地调用对方,谁都不用等待谁的答复,同样也不期待对方答复,因为如

WCF系列教程之消息交换模式之请求与答复模式(Request/Reply)

1.使用WCF请求与答复模式须知 (1).客户端调用WCF服务端需要等待服务端的返回,即使返回类型是void (2).相比Duplex来讲,这种模式强调的是客户端的被动接受,也就是说客户端接受到响应后,消息交换就结束了 (3).在这种模式下,服务端永远是服务端,客户端就是客户端,职责分明. (4).它是缺省的消息交换模式,设置OperationContract便可以设置为此种消息交换模式 2.代码示例 服务层接口IReqReplyService.cs代码如下: using System; usi

WCF初探-3:WCF消息交换模式之单向模式

1.单向模式(One-Way Calls): 在这种交换模式中,存在着如下的特征: 只有客户端发起请求,服务端并不会对请求进行回复 不能包含ref或者out类型的参数 没有返回值,返回类型只能为void 通过设置OperationContract的IsOneWay=True可以将满足要求的方法设置为这种消息交换模式 接 下来,我们通过实例来演示这种模式,首先新建一个WcfDemo1的解决方案,添加名称为Service的类库项目作为服务端,新建IOneWay接口和 OneWay类,由于单向模式中服

WCF初探-4:WCF消息交换模式之请求与答复模式

1.请求与答复模式( Request/Reply) 这种交换模式是使用最多的一中,它有如下特征: 调用服务方法后需要等待服务的消息返回,即便该方法返回 void 类型 相比Duplex来讲,这种模式强调的是客户端的被动接受,也就是说客户端接受到响应后,消息交换就结束了. 在这种模式下,服务端永远是服务端,客户端就是客户端,职责分明. 它是缺省的消息交换模式,设置OperationContract便可以设置为此种消息交换模式 接下来我们通过实例来演示一下,参照WCF消息交换模式之单向模式中的例子,

WCF 之 消息交换模式

消息交换模式(Message Exchange Pattern:MEP)在SOA中是一个重要的概念.MEP定义了参与者进行消息交换的模板,这是一个很抽象的定义.实际上我们可以这样理解MEP:消息交换模式(MEP)代表一系列的模板,它们定义了消息的发送者和接收者相互进行消息传输的次序.消息交换模式包括:数据报模式(Datagram).请求/回复模式(Request/Reply)和双工模式(Duplex). 1.数据报模式(Datagram) 数据报模式是最简单的消息交换模式,又称为发送/遗忘(Se

WCF消息交换模式之请求-响应模式

WCF的消息交换模式(MEP)有三种:请求/响应.单向模式和双工模式.WCF的默认MEP是请求/响应模式. 请求/响应模式操作签名代码如下,无需指定模式,默认就是. [OperationContractAttribute] string Hello(string greeting,string mesg); [OperationContractAttribute] void SaveMesg(string mesg); 请求/响应模式内容: 客户端可以传递一个或多个参数给服务操作方法,服务操作方

消息交换模式

WCF初探-15:WCF操作协定

前言: 在前面的文章中,我们定义服务协定时,在它的操作方法上都会加上OperationContract特性,此特性属于OperationContractAttribute 类,将OperationContract应用于方法,以指示该方法实现作为服务协定(由 ServiceContractAttribute 属性指定)一部分的服务操作.OperationContractAttribute 属性声明方法是服务协定中的操作. 只有具有 OperationContractAttribute 属性的方法可