wcf 雙工通訊


介紹 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("發送完畢");
                    }
                }
            }
        }


    }
}

 

上幾張雙工效果圖,服務實現 “有圖有真相”。

服務端 向客戶端發送請求。

 

客戶端向客戶端發送請求

 

分享一下我的源碼,有什么建議的朋友可以留言給我,相互討論,相互學習。

大家可以把 雙工通訊 理解成 “禮上往來”的通訊。

源碼下載

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM