介绍
但在双向操作模式中,不但客户端可以向服务器发送请求,服务器也可以主动向客户端广播消息(也就是回调客户端中的方法)。在WCF中,不是所有的绑定都可 以实现双向操作模式的,比如http协议,它本身就是基于请求-回复的传输模式,所以本质上是实现不了双向操作的。但WCF提供了 WSDualHttpBinding协议让我们在http上实现了双向操作。其实WSDualHttpBinding并没有违反http单向传输的本质, 它实际上是创建两个了通道,一个用于客户端向服务器请求,一个用于服务器向客户端广播,间接实现了双向操作。但《WCF服务编程》书上有 说,WSDualHttpBinding无法穿越客户端与服务器的重重障碍,所以不赞成使用WSDualHttpBinding来实现双向操作。
第一步
新建一个windows应用程序,取名Host
第2步:建立接口,IMessageService
using System;using System.Collections.Generic;using System.Linq;using System.ServiceModel;using System.Text;namespace Host{ //和单向操作相比,我们会发现服务契约上多了一行代码:[ServiceContract(CallbackContract = typeof(ICallBack))] [ServiceContract(CallbackContract = typeof(ICallBack))] public interface IMessageService { [OperationContract] void RegisterMes(); [OperationContract] int SendToAll(string name, string msg); ////// 文件上传 /// /// 字节数组 /// 文件后缀名 ///[OperationContract] int SentFile(byte[] data, string suffix); } public interface ICallBack { [OperationContract(IsOneWay = true)] void SendMessage(string name, string msg); [OperationContract(IsOneWay = true)] void Show(); }}
这时候引用 wcf组件
第 3步 建立 实现 MessageService
using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.ServiceModel;using System.Text;using System.Threading.Tasks;namespace Host{ [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public class MessageService : IMessageService, IDisposable { public static ListCallBackList { get; set; } public MessageService() { CallBackList = new List (); } public void RegisterMes() { var callback = OperationContext.Current.GetCallbackChannel (); string sessionid = OperationContext.Current.SessionId; Form1.fm1.listLine.Items.Add(sessionid);//服务端显示客户端的SessionId //OperationContext.Current.Channel.Closing += // delegate // { // lock (CallBackList) // { // CallBackList.Remove(callback); // } // }; OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing); CallBackList.Add(callback); } void Channel_Closing(object sender, EventArgs e) { lock (CallBackList) { CallBackList.Remove((ICallBack)sender); } } public void Dispose() { CallBackList.Clear(); } public int SendToAll(string name, string msg) { var list = Host.MessageService.CallBackList; if (list == null || list.Count == 0) return 0; lock (list) { Task.Factory.StartNew(new Action(() => { foreach (var client in list) { client.SendMessage(name, msg); } })); } return 1; } /// /// 文件上传 /// /// 字节数组 /// 文件后缀名 ///public int SentFile(byte[] data, string suffix) { DateTime dt = DateTime.Now; string filename = string.Format("{0:yyyyMMddHHmmssffff}", dt); File.WriteAllBytes(filename+suffix, data); return 0; } }}
第4步 配置 Appconfig
右键Appconfig 点击编辑wcf配置 进入元素浏览器(我把这个理解成 可视化配置),这样免去程序员手敲代码的麻烦。
上几张图
看到3张图没,新建了一个服务,添加了2个终结点
app.config生产代码
这个说下wcf 之 abc
- Address: 每一个WCF的Service都有一个唯一的地址。这个地址给出了Service的地址和传输协议(Transport Protocol)
- Binding:通信(Communication)的方式很多,同步的request/reply模式,非同步的fire-and- forget模式。消息可以单向或者双向的发送接收,可以立即发送或者把它放入到某一个队列中再处理。所供选择的传输协议也有Http, Tcp,P2P,IPC等。当要考虑Client/Server如何进行通讯的时候,除了考虑以上提到的几点之外,还有其它很多需要考虑的因素,如安全, 性能等。因此,简单来说,Binding只不过是微软提供的一组考虑比较周全、常用的封装好的通信方式。
- Contract:Contract描述了Service能提供的各种服务。Contract有四种,包括Service Contract, Data Contract, Fault Contract和Message Contract
第5步 建立客户端 取名 Client 添加 System.ServiceModel引用,添加wcf引用
这里强调一下wcf引用 怎么加入项目里,打开wcf服务端生成的bin文件,运行Host.exe文件
这时候 http://192.168.2.23:9998/Host 就可以访问了,取名叫WcfSvc
引用wcf服务 的时候 你发现 客户端 app.config配置文件已经生成了
在Client工程里新建 MyCallBack 类回调 服务端方法
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;namespace Client{ public class MyCallBack : WcfSvc.IMessageServiceCallback { public long count = 0; public void SendMessage(string name, string msg) { System.Threading.Interlocked.Increment(ref count); Form1.f.listMessage.Items.Add(string.Format("{0}:{1}:{2}", name, msg, count)); Form1.f.txtreceiving.Invoke(new Action(() => { Form1.f.txtreceiving.Text = count.ToString(); })); } public void Show() { Form1.f.listMessage.Invoke(new Action(() => { Form1.f.listMessage.Items.Add("show方法调用了"); })); } }}
这时候双工通讯搭建完毕,再来设计UI页面
这里设置ui控件有个技巧:默认控件是 私有属性,我把上线 这边文本框 改成 public,让他接受外面访问。
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.ServiceModel;using System.Text;using System.Windows.Forms;namespace Host{ public partial class Form1 : Form { public static Form1 fm1; private ServiceHost _host = null; public Form1() { InitializeComponent(); //构造函数初始化加载 fm1 =this; CheckForIllegalCrossThreadCalls = false; BindHost(); } private void btnStart_Click(object sender, EventArgs e) { BindHost(); } void BindHost() { try { _host = new ServiceHost(typeof(Host.MessageService)); _host.Open(); lbService.Text = string.Format("服务开启:{0}", DateTime.Now.ToString()); } catch (Exception ex) { ShowMessage(ex.Message); } } private void btnsend_Click(object sender, EventArgs e) { //发送内容 #region 备用 if (fm1 != null) { lock (MessageService.CallBackList) { foreach (ICallBack callback in MessageService.CallBackList) { callback.SendMessage("服务器发送: ", txtMsg.Text); } } } #endregion } ////// (公用的)信息显示 /// /// 消息内容 private void ShowMessage(string msg) { this.lbMessage.Text = msg; this.lbMessage.Visible = true; } }}
画客户端页面
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.ServiceModel;using System.Threading.Tasks;using System.IO;namespace Client{ public partial class Form1 : Form { public static Form1 f; public Form1() { InitializeComponent(); f = this; CheckForIllegalCrossThreadCalls = false; } WcfSvc.MessageServiceClient svc; private void Form1_Load(object sender, EventArgs e) { //页面初始化 //Online(); var client = new MyCallBack(); var ctx = new InstanceContext(client); svc = new WcfSvc.MessageServiceClient(ctx); svc.RegisterMes(); } private void button1_Click(object sender, EventArgs e) { //停止 isRun = false; } void ShowMessage(string msg) { this.lbError.Text = msg; this.lbError.Visible = true; } Task t; int sleeptime = 100; bool isRun = true; private void btnSend_Click(object sender, EventArgs e) { //发送 isRun = true; sleeptime = int.Parse(txtNum.Text); t = Task.Factory.StartNew(delegate { while (true) { if (isRun) { svc.SendToAll(txtUserName.Text, txtSendMessage.Text); System.Threading.Thread.Sleep(sleeptime); } else { break; } } }); } private void btnclear_Click(object sender, EventArgs e) { //清屏 this.listMessage.Items.Clear(); } private void btnUpload_Click(object sender, EventArgs e) { //文件上传 using (OpenFileDialog ofd = new OpenFileDialog()) { if (ofd.ShowDialog() == DialogResult.OK) { //FileStream fs = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read); byte[] data = File.ReadAllBytes(ofd.FileName); int r = svc.SentFile(data, Path.GetExtension(ofd.FileName)); if (r == 0) { MessageBox.Show("发送完毕"); } } } } }}
上几张双工效果图,服务实现 “有图有真相”。
服务端 向客户端发送请求。
客户端向客户端发送请求
分享一下我的源码,有什么建议的朋友可以留言给我,相互讨论,相互学习。
大家可以把 双工通讯 理解成 “礼上往来”的通讯。