zoukankan      html  css  js  c++  java
  • 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);                        //发布消息
         }
    }
    View Code

    接口里面只定义了订阅者(客户端)调用的订阅消息和取消订阅的方法,以及服务端调用客户端的回调方法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);
                }
            }
    
        }
    }
    View Code

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

    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>
    View Code

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

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

    注意:我们选择http://172.0.0.1:9998/WcfDuplexService地址,因为我们的服务已经采用了元数据地址发布,但是你们在引用的时候把配置文件和引用地址中的172.0.0.1改为

     localhost或者是127.0.0.1,再或者是你们本机的IP。要不然服务地址是不准确的,无法引用。(评论中有人出现了这个问题呢)

    接下来我们实现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);
            }
        }
    }
    View Code

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

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

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

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

    作者:wangweimutou
    关注技能:ASP.NET、ASP.NET MVC、ASP.NET WEB API、WCF、MSSQL、ORACLE
    感谢您阅读本文,如果您觉得有所收获,麻烦点一下右边的【推荐】,您的支持是对我最大的鼓励!
    由于作者能力有限,本文只做学习交流使用,如有不正之处,欢迎指出
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留原文链接,否则保留追究法律责任的权利
  • 相关阅读:
    线段树专辑—— pku 1436 Horizontally Visible Segments
    线段树专辑——pku 3667 Hotel
    线段树专辑——hdu 1540 Tunnel Warfare
    线段树专辑—— hdu 1828 Picture
    线段树专辑—— hdu 1542 Atlantis
    线段树专辑 —— pku 2482 Stars in Your Window
    线段树专辑 —— pku 3225 Help with Intervals
    线段树专辑—— hdu 1255 覆盖的面积
    线段树专辑—— hdu 3016 Man Down
    Ajax跨域访问
  • 原文地址:https://www.cnblogs.com/wangweimutou/p/4273086.html
Copyright © 2011-2022 走看看