介紹 wcf 單工通訊
但在雙向操作模式中,不但客戶端可以向服務器發送請求,服務器也可以主動向客戶端廣播消息(也就是回調客戶端中的方法)。在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); /// <summary> /// 文件上傳 /// </summary> /// <param name="data">字節數組</param> /// <param name="suffix">文件后綴名</param> /// <returns></returns> [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 List<ICallBack> CallBackList { get; set; } public MessageService() { CallBackList = new List<ICallBack>(); } public void RegisterMes() { var callback = OperationContext.Current.GetCallbackChannel<ICallBack>(); 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; } /// <summary> /// 文件上傳 /// </summary> /// <param name="data">字節數組</param> /// <param name="suffix">文件后綴名</param> /// <returns></returns> 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生產代碼
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.web> <compilation debug="true"/> </system.web> <system.serviceModel> <services> <service name="Host.MessageService"> <endpoint address="" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IMessageService" contract="Host.IMessageService"> </endpoint> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> <host> <baseAddresses> <add baseAddress="net.tcp://192.168.2.23:9999/Host/"/> <add baseAddress="http://192.168.2.23:9998/Host"/> </baseAddresses> </host> </service> </services> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="True"/> <serviceDebug includeExceptionDetailInFaults="False"/> </behavior> </serviceBehaviors> </behaviors> <bindings> <netTcpBinding> <binding name="NetTcpBinding_IMessageService" maxBufferSize="1024000000" maxReceivedMessageSize="1024000000" sendTimeout="00:00:30" transferMode="Buffered"> <security mode="None"> <transport clientCredentialType="Windows" /> <message clientCredentialType="Windows" /> </security> </binding> </netTcpBinding> </bindings> </system.serviceModel> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> </startup> </configuration>
這個說下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配置文件已經生成了
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <netTcpBinding> <binding name="NetTcpBinding_IMessageService"> <security mode="None" /> </binding> </netTcpBinding> </bindings> <client> <endpoint address="net.tcp://192.168.2.23:9999/Host/" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IMessageService" contract="WcfSvc.IMessageService" name="NetTcpBinding_IMessageService" /> </client> </system.serviceModel> </configuration>
在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 } /// <summary> /// (公用的)信息顯示 /// </summary> /// <param name="msg">消息內容</param> 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("發送完畢"); } } } } } }
上幾張雙工效果圖,服務實現 “有圖有真相”。
服務端 向客戶端發送請求。
客戶端向客戶端發送請求
分享一下我的源碼,有什么建議的朋友可以留言給我,相互討論,相互學習。
大家可以把 雙工通訊 理解成 “禮上往來”的通訊。