用Duplex实现消息广播
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)
- using System.ServiceModel;
- namespace WcfDuplexMessageService
- {
- [ServiceContract(CallbackContract=typeof(IClient))]
- public interface IMessageService
- {
- [OperationContract]
- void RegisterClient();
- }
- public interface IClient
- {
- [OperationContract(IsOneWay = true)]
- void SendMessage(string message);
- }
- }
(1) 定义的IClient用于客户端回调。
(2) 定义的RegisterClient()用于将客户端回调实例注册到服务端
2. 实现服务(工程WcfDuplexMessageService)
(1) 为了所有客户端都注册到一个服务对象上,所以定义服务端为Singleton实例模式:
InstanceContextMode=InstanceContextMode.Single (Singleton的实例在服务Host启动即实例化)
(2) 定义了一个static的List<IClient>统一保存客户端回调实例,并公开为Property,便于ServerUI能访问。
(3) 为了防止广播时不会因为客户端关闭而导致服务端异常,监听了Channel.Closing事件
客户端关闭(Channel被关闭)时就会触发这个事件,在此事件处理中移除该客户端回调实例。
- using System;
- using System.Collections.Generic;
- using System.ServiceModel;
- namespace WcfDuplexMessageService
- {
- [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
- public class MessageService : IMessageService, IDisposable
- {
- public static List<IClient> ClientCallbackList { get; set; }
- public MessageService()
- {
- ClientCallbackList = new List<IClient>();
- }
- public void RegisterClient()
- {
- var client = OperationContext.Current.GetCallbackChannel<IClient>();
- var id = OperationContext.Current.SessionId;
- Console.WriteLine("{0} registered.", id);
- OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
- ClientCallbackList.Add(client);
- }
- void Channel_Closing(object sender, EventArgs e)
- {
- lock (ClientCallbackList)
- {
- ClientCallbackList.Remove((IClient)sender);
- }
- }
- public void Dispose()
- {
- ClientCallbackList.Clear();
- }
- }
- }
3. 服务端Host兼UI实现 
Broadcast 按钮按下时,遍历 WcfDuplexMessageService.MessageService.ClientCallbackList 回调。
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Windows.Forms;
- using System.ServiceModel;
- namespace WcfDuplexMessageSvcHost
- {
- public partial class Form1 : Form
- {
- public Form1()
- {
- InitializeComponent();
- }
- private ServiceHost _host = null;
- private void Form1_Load(object sender, EventArgs e)
- {
- _host = new ServiceHost(typeof(WcfDuplexMessageService.MessageService));
- _host.Open();
- this.label1.Text = "MessageService Opened.";
- }
- private void Form1_FormClosing(object sender, FormClosingEventArgs e)
- {
- if (_host != null)
- {
- _host.Close();
- IDisposable host = _host as IDisposable;
- host.Dispose();
- }
- }
- private void button1_Click(object sender, EventArgs e)
- {
- var list = WcfDuplexMessageService.MessageService.ClientCallbackList;
- if (list == null || list.Count == 0)
- return;
- lock (list)
- {
- foreach (var client in list)
- {
- // Broadcast
- client.SendMessage(this.textBox1.Text);
- }
- }
- }
- }
- }
配置:
为了客户端能直接通过公开的Metadata生成proxy,配置文件中加上:
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
因为元数据公开的服务(IMetadataExchange)使用的是mexHttpBinding,而Duplex使用的是netTcpBinding,所以需要追加http协议对应的BaseAddress:http://localhost:9998/WcfDuplexMessageService
或者修改元数据公开服务的Binding方式:改为mexTcpBinding
- <?xml version="1.0"?>
- <configuration>
- <system.web>
- <compilation debug="true"/>
- </system.web>
- <system.serviceModel>
- <services>
- <service name="WcfDuplexMessageService.MessageService">
- <endpoint address="" binding="netTcpBinding" bindingConfiguration="" contract="WcfDuplexMessageService.IMessageService">
- <identity>
- <dns value="localhost"/>
- </identity>
- </endpoint>
- <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
- <host>
- <baseAddresses>
- <add baseAddress="net.tcp://localhost:9999/WcfDuplexMessageService/"/>
- <add baseAddress="http://localhost:9998/WcfDuplexMessageService"/>
- </baseAddresses>
- </host>
- </service>
- </services>
- <behaviors>
- <serviceBehaviors>
- <behavior>
- <serviceMetadata httpGetEnabled="True"/>
- <serviceDebug includeExceptionDetailInFaults="False"/>
- </behavior>
- </serviceBehaviors>
- </behaviors>
- </system.serviceModel>
- <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
4. 客户端实现
(1) 通过Add Service Reference生成客户端Proxy
(2) 实现 WcfSvc.IMessageServiceCallback (Client.cs)
- using System;
- namespace WcfDuplexMessageClient
- {
- public class Client : WcfSvc.IMessageServiceCallback
- {
- public void SendMessage(string message)
- {
- Console.WriteLine("[ClientTime{0:HHmmss}]Service Broadcast:{1}", DateTime.Now, message);
- }
- }
- }
(3) 启动客户端,调用服务端的注册方法:WcfSvc.MessageServiceClient,将客户端的Client实例注册到服务。
- using System;
- using System.ServiceModel;
- namespace WcfDuplexMessageClient
- {
- class Program
- {
- static void Main(string[] args)
- {
- var client = new Client();
- var ctx = new InstanceContext(client);
- var svc = new WcfSvc.MessageServiceClient(ctx);
- svc.RegisterClient();
- Console.Read();
- }
- }
- }
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实现消息广播的更多相关文章
- Consul实现原理系列文章2: 用Gossip来做集群成员管理和消息广播
工作中用到了Consul来做服务发现,之后一段时间里,我会陆续发一些文章来讲述Consul实现原理.这篇文章会讲述Consul是如何使用Gossip来做集群成员管理和消息广播的. Consul使用Go ...
- Eureka 系列(06)消息广播(下):TaskDispacher 之 Acceptor - Worker 模式
Eureka 系列(06)消息广播(下):TaskDispacher 之 Acceptor - Worker 模式 [TOC] Spring Cloud 系列目录 - Eureka 篇 Eureka ...
- Eureka 系列(05)消息广播(上):消息广播原理分析
Eureka 系列(05)消息广播(上):消息广播原理分析 [TOC] 0. Spring Cloud 系列目录 - Eureka 篇 首先回顾一下客户端服务发现的流程,在上一篇 Eureka 系列( ...
- SpringCloud 2020.0.4 系列之 Stream 消息广播 与 消息分组 的实现
1. 概述 老话说的好:事情太多,做不过来,就先把事情记在本子上,然后理清思路.排好优先级,一件一件的去完成. 言归正传,今天我们来聊一下 SpringCloud 的 Stream 组件,Spring ...
- beetle 2.7海量消息广播测试
由于client资源限制,只进行了300物体互动广播测试:物体活动频率是每秒20次,服务器每秒转发的消息量大概180W条. 转发消息结构: class Po : IMessage { public i ...
- Android 消息广播Intent传递数据
1.创建布局文件activity_broadcast.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk ...
- SignalR的简单实现消息广播
之前由于一个项目的需要(简单说一下,一个网页游戏,裁判的页面点击开始按钮,玩家便可以开始游戏),研究了很久,最终一个同事跟我推荐了SignalR.距离项目结束已经有一段时间了,再来回顾一下Signal ...
- WCF开发教程资源收集
WCF开发教程资源收集 1.蒋金楠,网名Artech的博客 [原创]我的WCF之旅(1):创建一个简单的WCF程序[原创]我的WCF之旅(2):Endpoint Overview[原创]我的WCF之旅 ...
- Delphi消息的广播方式(先RegisterWindowMessage,后SendMessage HWND_BROADCAST,最后改写接收窗口的WndProc)
///////消息广播只能将消息传递到接收消息的主程序中,MDIChild窗体不能接收到广播消息:///////// unit Unit1; interface uses Windows, Messa ...
随机推荐
- Linux主流架构运维工作简单剖析
转载:http://wgkgood.blog.51cto.com/1192594/1586259 随着IT运维的不断发展,尤其的Linux的飞速发展,越来越多的企业开始使用Linux操作系统平台,例如 ...
- Axure 地区选择(选择省份之后可以选择对应的地级市)
百度网盘:http://pan.baidu.com/s/1c1ZjUPq
- N皇后问题【递归求解】
n皇后问题:输入整数n, 要求n个国际象棋的皇后,摆在n*n的棋盘上,互相不能攻击,输出全部方案. 输入一个正整数N,则程序输出N皇后问题的全部摆法.输出结果里的每一行都代表一种摆法.行里的第i个数字 ...
- mysql workbench图形化mysql管理工具
MYSQL官网也推出了针对Linux的图形化的连接工具-MySQL Workbench.MySQL Workbench不仅仅是一个简单的MySQL客户端.简而言之,Workbench是一个跨平台的 ( ...
- Javascript判断Crontab表达式是否合法
这段时间在做Quartz任务调度,使用的Crontab表达式实现的.Crontab由前端页面输入,作为参数穿入后台. 虽然Quartz具有校验Crontab表达式的方法,如下: boolean cro ...
- 【phpstudy】安装Oracle 客户端 并连接
参考连接:https://blog.csdn.net/liuquan007/article/details/77508518 phpstudy2016是32位版 phpstudy2014是64位版本[ ...
- 一个还不错的gridview 样式【Z】
<style type="text/css"> <!-- .datable {background-color: #9FD6FF; color:#333333; ...
- Swift3 substring几种常用用法
举例: " let length = str.characters.count //截取前四位 )) //截取后2位(两种方法) )) )) //截取中间4位,从第2位开始(二种方法) le ...
- 我收藏的技术知识图(每张都是大图)关于XX背后的知识、技术图,例如:Linux、Nginx架构、PHP知识卡、机会、HTML5移动、Android系统架构、YII架构的典型流程、Css知识表
我收藏的技术知识图(每张都是大图) HTML5Linux/Unix系统设计思想读书笔记 LinuxMVCJava线程MVCSpring MVCCSS3Nginx架构VimCliCommandsPHP知 ...
- CMFCPropertyGridProperty用法
MFCPropertyGridCtrl 是VC 2008 pack中的控件类. CMFCPropertyGridProperty这个控件类中的属性值类类. 针对修改属性后,对属性值改变的消息处理: 方 ...