WCF中定义3种消息交换模式: 1. Request/Reply; 2. One-Way; 3. Duplex。  
Request/Reply 是缺省模式,即同步调用。在调用服务方法后需要等待服务的消息返回,即便该方法返回 void 类型。 
One-Way 这种方式在调用方法后会立即返回。需要注意的是 One-Way 不能用在非void,或者包含 out/ref 参数的方法上,会导致抛出 InvalidOperationException 异常。 
Duplex 又称为双工通信,实现起来比前两种来说要稍微复杂些。(1) ServiceContract 中指定 Callback类型; (2) 对于回调操作,指定[OperationContract(IsOneWay=true)] ; (3) 服务契约中通过 OperationContext.Current.GetCallbackChannel 来获得客户端 Callback 实例。

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

接下来,介绍如何用WCF的Duplex消息交换实现服务端对客户端的广播。 
  
1. 定义服务契约 (创建WCF Service Library工程:WcfDuplexMessageService)

  1. using System.ServiceModel;
  2. namespace WcfDuplexMessageService
  3. {
  4. [ServiceContract(CallbackContract=typeof(IClient))]
  5. public interface IMessageService
  6. {
  7. [OperationContract]
  8. void RegisterClient();
  9. }
  10. public interface IClient
  11. {
  12. [OperationContract(IsOneWay = true)]
  13. void SendMessage(string message);
  14. }
  15. }

(1) 定义的IClient用于客户端回调。
(2) 定义的RegisterClient()用于将客户端回调实例注册到服务端

2. 实现服务(工程WcfDuplexMessageService)
(1) 为了所有客户端都注册到一个服务对象上,所以定义服务端为Singleton实例模式: 
      InstanceContextMode=InstanceContextMode.Single (Singleton的实例在服务Host启动即实例化)
(2) 定义了一个static的List<IClient>统一保存客户端回调实例,并公开为Property,便于ServerUI能访问。
(3) 为了防止广播时不会因为客户端关闭而导致服务端异常,监听了Channel.Closing事件
     客户端关闭(Channel被关闭)时就会触发这个事件,在此事件处理中移除该客户端回调实例。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ServiceModel;
  4. namespace WcfDuplexMessageService
  5. {
  6. [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
  7. public class MessageService : IMessageService, IDisposable
  8. {
  9. public static List<IClient> ClientCallbackList { get; set; }
  10. public MessageService()
  11. {
  12. ClientCallbackList = new List<IClient>();
  13. }
  14. public void RegisterClient()
  15. {
  16. var client = OperationContext.Current.GetCallbackChannel<IClient>();
  17. var id = OperationContext.Current.SessionId;
  18. Console.WriteLine("{0} registered.", id);
  19. OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
  20. ClientCallbackList.Add(client);
  21. }
  22. void Channel_Closing(object sender, EventArgs e)
  23. {
  24. lock (ClientCallbackList)
  25. {
  26. ClientCallbackList.Remove((IClient)sender);
  27. }
  28. }
  29. public void Dispose()
  30. {
  31. ClientCallbackList.Clear();
  32. }
  33. }
  34. }

3. 服务端Host兼UI实现 

Broadcast 按钮按下时,遍历 WcfDuplexMessageService.MessageService.ClientCallbackList 回调。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Windows.Forms;
  5. using System.ServiceModel;
  6. namespace WcfDuplexMessageSvcHost
  7. {
  8. public partial class Form1 : Form
  9. {
  10. public Form1()
  11. {
  12. InitializeComponent();
  13. }
  14. private ServiceHost _host = null;
  15. private void Form1_Load(object sender, EventArgs e)
  16. {
  17. _host = new ServiceHost(typeof(WcfDuplexMessageService.MessageService));
  18. _host.Open();
  19. this.label1.Text = "MessageService Opened.";
  20. }
  21. private void Form1_FormClosing(object sender, FormClosingEventArgs e)
  22. {
  23. if (_host != null)
  24. {
  25. _host.Close();
  26. IDisposable host = _host as IDisposable;
  27. host.Dispose();
  28. }
  29. }
  30. private void button1_Click(object sender, EventArgs e)
  31. {
  32. var list = WcfDuplexMessageService.MessageService.ClientCallbackList;
  33. if (list == null || list.Count == 0)
  34. return;
  35. lock (list)
  36. {
  37. foreach (var client in list)
  38. {
  39. // Broadcast
  40. client.SendMessage(this.textBox1.Text);
  41. }
  42. }
  43. }
  44. }
  45. }

配置:
为了客户端能直接通过公开的Metadata生成proxy,配置文件中加上:
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
因为元数据公开的服务(IMetadataExchange)使用的是mexHttpBinding,而Duplex使用的是netTcpBinding,所以需要追加http协议对应的BaseAddress:http://localhost:9998/WcfDuplexMessageService
或者修改元数据公开服务的Binding方式:改为mexTcpBinding

  1. <?xml version="1.0"?>
  2. <configuration>
  3. <system.web>
  4. <compilation debug="true"/>
  5. </system.web>
  6. <system.serviceModel>
  7. <services>
  8. <service name="WcfDuplexMessageService.MessageService">
  9. <endpoint address="" binding="netTcpBinding" bindingConfiguration="" contract="WcfDuplexMessageService.IMessageService">
  10. <identity>
  11. <dns value="localhost"/>
  12. </identity>
  13. </endpoint>
  14. <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
  15. <host>
  16. <baseAddresses>
  17. <add baseAddress="net.tcp://localhost:9999/WcfDuplexMessageService/"/>
  18. <add baseAddress="http://localhost:9998/WcfDuplexMessageService"/>
  19. </baseAddresses>
  20. </host>
  21. </service>
  22. </services>
  23. <behaviors>
  24. <serviceBehaviors>
  25. <behavior>
  26. <serviceMetadata httpGetEnabled="True"/>
  27. <serviceDebug includeExceptionDetailInFaults="False"/>
  28. </behavior>
  29. </serviceBehaviors>
  30. </behaviors>
  31. </system.serviceModel>
  32. <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>

4. 客户端实现
(1) 通过Add Service Reference生成客户端Proxy

(2) 实现 WcfSvc.IMessageServiceCallback (Client.cs)

  1. using System;
  2. namespace WcfDuplexMessageClient
  3. {
  4. public class Client : WcfSvc.IMessageServiceCallback
  5. {
  6. public void SendMessage(string message)
  7. {
  8. Console.WriteLine("[ClientTime{0:HHmmss}]Service Broadcast:{1}", DateTime.Now, message);
  9. }
  10. }
  11. }

(3) 启动客户端,调用服务端的注册方法:WcfSvc.MessageServiceClient,将客户端的Client实例注册到服务。

  1. using System;
  2. using System.ServiceModel;
  3. namespace WcfDuplexMessageClient
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. var client = new Client();
  10. var ctx = new InstanceContext(client);
  11. var svc = new WcfSvc.MessageServiceClient(ctx);
  12. svc.RegisterClient();
  13. Console.Read();
  14. }
  15. }
  16. }

OK,运行一下:

补充:
1. 如果去掉回调契约的IsOneWay属性,将会导致服务端引发InvalidOperationException异常。关于Duplex的消息交换定制还可以看看这篇blog:http://www.cnblogs.com/xinhaijulan/archive/2011/01/09/1931272.html
2. XP的IIS 5.x 使用wsDuplexBinding时, 因为回调的服务监听地址默认采用是80,而80正是IIS独占的监听端口。此时会出现AddressAlreadyInUseException异常。为了解决这个问题,需要修改回调服务监听地址:wsDuplexBinding的clientBaseAddress

Task task = new Task(() => Listen(cts.Token), cts.Token);

http://blog.csdn.net/fangxing80/article/details/6142861

用Duplex实现消息广播的更多相关文章

  1. Consul实现原理系列文章2: 用Gossip来做集群成员管理和消息广播

    工作中用到了Consul来做服务发现,之后一段时间里,我会陆续发一些文章来讲述Consul实现原理.这篇文章会讲述Consul是如何使用Gossip来做集群成员管理和消息广播的. Consul使用Go ...

  2. Eureka 系列(06)消息广播(下):TaskDispacher 之 Acceptor - Worker 模式

    Eureka 系列(06)消息广播(下):TaskDispacher 之 Acceptor - Worker 模式 [TOC] Spring Cloud 系列目录 - Eureka 篇 Eureka ...

  3. Eureka 系列(05)消息广播(上):消息广播原理分析

    Eureka 系列(05)消息广播(上):消息广播原理分析 [TOC] 0. Spring Cloud 系列目录 - Eureka 篇 首先回顾一下客户端服务发现的流程,在上一篇 Eureka 系列( ...

  4. SpringCloud 2020.0.4 系列之 Stream 消息广播 与 消息分组 的实现

    1. 概述 老话说的好:事情太多,做不过来,就先把事情记在本子上,然后理清思路.排好优先级,一件一件的去完成. 言归正传,今天我们来聊一下 SpringCloud 的 Stream 组件,Spring ...

  5. beetle 2.7海量消息广播测试

    由于client资源限制,只进行了300物体互动广播测试:物体活动频率是每秒20次,服务器每秒转发的消息量大概180W条. 转发消息结构: class Po : IMessage { public i ...

  6. Android 消息广播Intent传递数据

    1.创建布局文件activity_broadcast.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk ...

  7. SignalR的简单实现消息广播

    之前由于一个项目的需要(简单说一下,一个网页游戏,裁判的页面点击开始按钮,玩家便可以开始游戏),研究了很久,最终一个同事跟我推荐了SignalR.距离项目结束已经有一段时间了,再来回顾一下Signal ...

  8. WCF开发教程资源收集

    WCF开发教程资源收集 1.蒋金楠,网名Artech的博客 [原创]我的WCF之旅(1):创建一个简单的WCF程序[原创]我的WCF之旅(2):Endpoint Overview[原创]我的WCF之旅 ...

  9. Delphi消息的广播方式(先RegisterWindowMessage,后SendMessage HWND_BROADCAST,最后改写接收窗口的WndProc)

    ///////消息广播只能将消息传递到接收消息的主程序中,MDIChild窗体不能接收到广播消息:///////// unit Unit1; interface uses Windows, Messa ...

随机推荐

  1. java 遍历Map的四种方式

      java 遍历Map的四种方式 CreationTime--2018年7月16日16点15分 Author:Marydon 一.迭代key&value 第一种方式:迭代entrySet 1 ...

  2. 【BI】OLTP与OLAP的区别

    概念 OLTP:联机事务处理(On-Line transaction Processing) OLAP:联机分析处理(On-Line Analytical Processing) (1)OLTP是传统 ...

  3. Excel Vlookup 列查找函数

    列查找函数语法:vlookup(lookup_value,table_array,col_index_num,[range_lookup]) lookup_value:要查找的值,数值.引用或文本字符 ...

  4. 微软微服务架构eShopOnContainers

    为了推广.Net Core,微软为我们提供了一个开源Demo-eShopOnContainers,这是一个使用Net Core框架开发的,跨平台(几乎涵盖了所有平台,windows.mac.linux ...

  5. 俄罗斯方块-C语言-详注版

    代码地址如下:http://www.demodashi.com/demo/14818.html 俄罗斯方块-C语言-详注版 概述 本文详述了C语言版俄罗斯方块游戏的原理以及实现方法,对游戏代码进行了详 ...

  6. 微信小程序开发学习资料

    作者:初雪链接:https://www.zhihu.com/question/50907897/answer/128494332来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...

  7. 自己动手做——邮件客户端FrankMail

    一.预备知识 二.需求分析 三.编码 四.交付 软件界面: 发送结果: --EOF--

  8. 【TP5.0】tp5.0实现连接多个数据库,实现类似3.2M(‘table’,'prefix_','db_config2')的CURD操作

    1.db_connect的name链式操作,类似于3.2的M('table','prefix_','db_config2') /** * db_connect的name链式操作,类似于3.2的M('t ...

  9. html模板实现银幕滚动效果<marquee>标签使用

    该标签不是HTML3.2的一部分,并且只支持MSIE3以后内核,所以如果你使用非IE内核浏览器(如:Netscape)可能无法看到下面一些很有意思的效果该标签是个容器标签语法: <marquee ...

  10. 如何开发一个基于 Docker 的 Python 应用

    前言 Python 家族成员繁多,解决五花八门的业务需求.这里将通过 Python 明星项目 IPython Notebook,使其容器化,让大家掌握基础的 Docker 使用方法. IPython ...