双工通讯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文件,定义服务接口和回调接口,代码如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.ServiceModel;
  6.  
  7. namespace Publisher
  8. {
  9.  
  10. [ServiceContract(CallbackContract = typeof(IPublisherEvents))]
  11. public interface IPublisher
  12. {
  13. [OperationContract(IsOneWay = true)]
  14. void Subscriber(string clientID,string clientName); //订阅消息
  15.  
  16. [OperationContract(IsOneWay = true)]
  17. void UnSubscriber(string clientID, string clientName); //取消订阅
  18. }
  19.  
  20. public interface IPublisherEvents
  21. {
  22. [OperationContract(IsOneWay = true)]
  23. void PublishMessage(string message); //发布消息
  24. }
  25. }

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Windows.Forms;
  4. using System.ServiceModel;
  5. using System.Threading;
  6.  
  7. namespace Publisher
  8. {
  9.  
  10. [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
  11. public partial class FormPublisher : Form, IPublisher, IDisposable
  12. {
  13. //定义回调客户端集合
  14. public static List<IPublisherEvents> ClientCallbackList { get; set; }
  15.  
  16. public FormPublisher()
  17. {
  18. InitializeComponent();
  19. ClientCallbackList = new List<IPublisherEvents>();
  20. }
  21.  
  22. //寄宿服务
  23. private ServiceHost _host = null;
  24. private void FormPublisher_Load(object sender, EventArgs e)
  25. {
  26. _host = new ServiceHost(typeof(Publisher.FormPublisher));
  27. _host.Open();
  28. this.label1.Text = "MessageService Opened.";
  29. }
  30.  
  31. //关闭窗体
  32. private void FormPublisher_FormClosing(object sender, FormClosingEventArgs e)
  33. {
  34. if (_host != null)
  35. {
  36. _host.Close();
  37. IDisposable host = _host as IDisposable;
  38. host.Dispose();
  39. }
  40. }
  41.  
  42. //发布消息
  43. private void btn_Publish_Click(object sender, EventArgs e)
  44. {
  45.  
  46. var list =Publisher.FormPublisher.ClientCallbackList;
  47. if (list == null || list.Count == )
  48. return;
  49. lock (list)
  50. {
  51. foreach (var client in list)
  52. {
  53. client.PublishMessage(this.txt_Message.Text);
  54. }
  55. }
  56.  
  57. }
  58.  
  59. //实现订阅
  60. public void Subscriber(string clientID, string clientName)
  61. {
  62. var client = OperationContext.Current.GetCallbackChannel<IPublisherEvents>();
  63. var sessionid = OperationContext.Current.SessionId;
  64. MessageBox.Show( string.Format("客户端{0} 开始订阅消息。", clientName));
  65. OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
  66. ClientCallbackList.Add(client);
  67.  
  68. }
  69.  
  70. //取消订阅
  71. public void UnSubscriber(string clientID, string clientName)
  72. {
  73. var client = OperationContext.Current.GetCallbackChannel<IPublisherEvents>();
  74. var sessionid = OperationContext.Current.SessionId;
  75. MessageBox.Show(string.Format("客户端{0}取消订阅消息", clientName));
  76. OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
  77. ClientCallbackList.Remove(client);
  78. }
  79.  
  80. //关闭通道,移除回调客户端
  81. void Channel_Closing(object sender, EventArgs e)
  82. {
  83. lock (ClientCallbackList)
  84. {
  85. ClientCallbackList.Remove((IPublisherEvents)sender);
  86. }
  87. }
  88.  
  89. }
  90. }

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

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

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

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

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

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

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3. <system.web>
  4. <compilation debug="true"/>
  5. </system.web>
  6. <system.serviceModel>
  7.  
  8. <services>
  9. <service name="Publisher.FormPublisher">
  10. <endpoint address="" binding="netTcpBinding" bindingConfiguration="netTcpExpenseService_ForSupplier" contract="Publisher.IPublisher">
  11. <identity>
  12. <dns value="localhost"/>
  13. </identity>
  14. </endpoint>
  15. <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
  16. <host>
  17. <baseAddresses>
  18. <add baseAddress="net.tcp://172.0.0.1:9999/WcfDuplexService/"/>
  19. <add baseAddress="http://172.0.0.1:9998/WcfDuplexService"/>
  20. </baseAddresses>
  21. </host>
  22. </service>
  23. </services>
  24.  
  25. <behaviors>
  26. <serviceBehaviors>
  27. <behavior>
  28. <serviceMetadata httpGetEnabled="True"/>
  29. <serviceDebug includeExceptionDetailInFaults="False"/>
  30. </behavior>
  31. </serviceBehaviors>
  32. </behaviors>
  33.  
  34. <bindings>
  35. <netTcpBinding>
  36. <binding name="netTcpExpenseService_ForSupplier" closeTimeout="00:01:00"
  37. openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
  38. transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
  39. hostNameComparisonMode="StrongWildcard" listenBacklog="10"
  40. maxBufferPoolSize="2147483647" maxBufferSize="2147483647" maxConnections="10"
  41. maxReceivedMessageSize="2147483647">
  42. <readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="2147483647"
  43. maxBytesPerRead="4096" maxNameTableCharCount="16384" />
  44. <reliableSession ordered="true" inactivityTimeout="00:10:00"
  45. enabled="false" />
  46. <security mode="None">
  47. <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
  48. <message clientCredentialType="Windows" />
  49. </security>
  50. </binding>
  51. </netTcpBinding>
  52. </bindings>
  53.  
  54. </system.serviceModel>
  55.  
  56. </configuration>

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

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

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

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

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

  1. using System;
  2. using System.Windows.Forms;
  3. using System.ServiceModel;
  4. using System.Threading;
  5. using Subscriber.WcfDuplexService;
  6.  
  7. namespace Subscriber
  8. {
  9. public partial class FormSubscriber : Form, IPublisherCallback
  10. {
  11. PublisherClient proxy = null;
  12.  
  13. public FormSubscriber()
  14. {
  15. InitializeComponent();
  16. InstanceContext instance = new InstanceContext(this);
  17. proxy = new PublisherClient(instance);
  18.  
  19. btn_cancle.Enabled = false;
  20. }
  21.  
  22. //实现客户端回调函数
  23. public void PublishMessage(string message)
  24. {
  25. string msg = string.Format("来自服务端的广播消息 : {0}",message);
  26. lst_getMsg.Items.Add(msg);
  27. }
  28.  
  29. //订阅消息
  30. private void btn_ok_Click(object sender, EventArgs e)
  31. {
  32. btn_ok.Enabled = false;
  33. btn_cancle.Enabled = true;
  34.  
  35. string ClientID = System.Guid.NewGuid().ToString();
  36. string ClientName = this.textBox1.Text;
  37. proxy.Subscriber(ClientID, ClientName);
  38. }
  39.  
  40. //取消订阅
  41. private void btn_cancle_Click(object sender, EventArgs e)
  42. {
  43. btn_ok.Enabled = true;
  44. btn_cancle.Enabled = false;
  45.  
  46. string ClientID = System.Guid.NewGuid().ToString();
  47. string ClientName = this.textBox1.Text;
  48. proxy.UnSubscriber(ClientID, ClientName);
  49. }
  50. }
  51. }

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

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

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

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

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

  1. WCF消息交换模式之双工通讯(Duplex)

    WCF消息交换模式之双工通讯(Duplex) 双工通讯Duplex具有以下特点: 1它可以在处理完请求之后,通过请求客户端中的回调进行响应操作 2.消息交换过程中,服务端和客户端角色会发生调换 3.服 ...

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

    老老实实学WCF 第十篇 消息通信模式(下) 双工 在前一篇的学习中,我们了解了单向和请求/应答这两种消息通信模式.我们知道可以通过配置操作协定的IsOneWay属性来改变模式.在这一篇中我们来研究双 ...

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

    原文:[老老实实学WCF] 第十篇 消息通信模式(下) 双工 第十篇 消息通信模式(下) 双工 在前一篇的学习中,我们了解了单向和请求/应答这两种消息通信模式.我们知道可以通过配置操作协定的IsOne ...

  4. WCF学习笔记之消息交换模式

    在WCF通信中,有三种消息交换模式,OneWay(单向模式), Request/Reponse(请求回复模式), Duplex(双工通信模式)这三种通信方式.下面对这三种消息交换模式进行讲解. 1. ...

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

    1.使用WCF请求与答复模式须知 (1).客户端调用WCF服务端需要等待服务端的返回,即使返回类型是void (2).相比Duplex来讲,这种模式强调的是客户端的被动接受,也就是说客户端接受到响应后 ...

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

    单向模式(One-Way Calls): 在这种交换模式中,存在着如下的特征: 只有客户端发起请求,服务端并不会对请求进行回复 不能包含ref或者out类型的参数 没有返回值,返回类型只能为void ...

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

    请求与答复模式( Request/Reply) 这种交换模式是使用最多的一中,它有如下特征: 调用服务方法后需要等待服务的消息返回,即便该方法返回 void 类型 相比Duplex来讲,这种模式强调的 ...

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

    WCF的消息交换模式(MEP)有三种:请求/响应.单向模式和双工模式.WCF的默认MEP是请求/响应模式. 请求/响应模式操作签名代码如下,无需指定模式,默认就是. [OperationContrac ...

  9. WCF系列教程之WCF消息交换模式之单项模式

    1.使用WCF单项模式须知 (1).WCF服务端接受客户端的请求,但是不会对客户端进行回复 (2).使用单项模式的服务端接口,不能包含ref或者out类型的参数,至于为什么,请参考C# ref与out ...

随机推荐

  1. 记录重置css样式

    ;} ol,ul{;;} table {; } caption, th, td { font-weight: normal; text-align: left; } a img, iframe { b ...

  2. curl常用选项详解

    curl常用选项详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 又是下班的时间了,让我们一起来学习一下今天的Linux命令吧~我一半只把自己常用的参数列出来,其他的有但是我们几 ...

  3. 5.echo(),print(),print_r()的区别

    echo是PHP语句, print和print_r是函数,语句没有返回值,函数可以有返回值(即便没有用) print()    只能打印出简单类型变量的值(如int,string) print_r() ...

  4. JS中同名函数有效执行顺序

    html中如果出现函数同名时:如果有多个外部引入的js文件,例如a.js和b.js(引入顺序假定是a.js,然后是b.js),同时html中本身也有内部的js.那么针对 出现函数名一样的情况时,无论他 ...

  5. 《zw版·Halcon-delphi系列原创教程》 Halcon分类函数012,polygon,多边形

    <zw版·Halcon-delphi系列原创教程> Halcon分类函数012,polygon,多边形 为方便阅读,在不影响说明的前提下,笔者对函数进行了简化: :: 用符号“**”,替换 ...

  6. mysql之各种命令总结

    1:使用SHOW语句找出在服务器上当前存在什么数据库:mysql> SHOW DATABASES;2:2.创建一个数据库MYSQLDATAmysql> CREATE DATABASE MY ...

  7. 数据可视化:Echart中k图实现动态阈值报警及实时更新数据

    1 目标 使用Echart的k图展现上下阈值,并且当真实值超过上阈值或低于下阈值时候,标红报警. 2 实现效果 如下:

  8. php缓冲区 sapi缓冲区

    <?php#设置php.ini中output_buffering = 32#使用apache可以看到效果 #nginx+php-fpm看不到效果 nginx缓存 sockets通信问题?#imp ...

  9. 谈谈对AOP的理解

    Aspect Oriented Programming  面向切面编程.解耦是程序员编码开发过程中一直追求的.AOP也是为了解耦所诞生. 具体思想是:定义一个切面,在切面的纵向定义处理方法,处理完成之 ...

  10. EventBus使用详解(二)——EventBus使用进阶

    一.概述 前一篇给大家装简单演示了EventBus的onEventMainThread()函数的接收,其实EventBus还有另外有个不同的函数,他们分别是: 1.onEvent2.onEventMa ...