WCF初探-5:WCF消息交換模式之雙工通訊(Duplex)


雙工通訊Duplex具有以下特點:

1它可以在處理完請求之后,通過請求客戶端中的回調進行響應操作

2.消息交換過程中,服務端和客戶端角色會發生調換

3.服務端處理完請求后,返回給客戶端的不是reply,而是callback請求。

4.Duplex模式對Bindding有特殊的要求,它要求支持Duplex MEP(Message Exchange Pattern),如WSDualHttpBinding和NetTcpBinding

注意:在WCF預定義綁定類型中,WSDualHttpBinding和NetTcpBinding均提供了對雙工通信的支持,但是兩者在對雙工通信的實現機制上卻有本質的區別。WSDualHttpBinding是基於HTTP傳輸協議的;而HTTP協議本身是基於請求-回復的傳輸協議,基於HTTP的通道本質上都是單向的。WSDualHttpBinding實際上創建了兩個通道,一個用於客戶端向服務端的通信,而另一個則用於服務端到客戶端的通信,從而間接地提供了雙工通信的實現。而NetTcpBinding完全基於支持雙工通信的TCP協議。

我今天的實例講的就是雙工通訊的一個使用場景訂閱-發布模式,此時消息的雙方變成了訂閱者和發布者。訂閱者有兩個操作(訂閱消息、取消訂閱),當訂閱者訂閱消息后,發布者就開始向訂閱者廣播消息,當訂閱者取消訂閱后,就不會接收到廣播的消息。具體如下圖所示:

接下來我們我們創建基於WCF的雙工通訊的訂閱與發布模式的服務。工程結構如下圖所示:

Publisher(發布者)和Subscriber(訂閱者)都是Winform工程,我們把發布者作為服務端,訂閱者作為客戶端,發布者還需要承載寄宿服務。如下圖設置好發布者和訂閱者的界面,

發布者有一個寄宿服務的lable顯示服務是否寄宿成功,一個消息文本框和一個發布按鈕,輸入文本后,點擊發布就可以向訂閱的客戶端廣播消息。

訂閱者的界面上有一個消息接收的listbox,以及訂閱消息和取消訂閱按鈕,還有一個輸入客戶端名稱的文本框,界面如下圖所示:

接下來我們開始實際的代碼操作,首先完成發布者(服務端)的代碼實現,創建IPublisher.cs文件,定義服務接口和回調接口,代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Publisher
{

     [ServiceContract(CallbackContract = typeof(IPublisherEvents))]
     public interface IPublisher
     {
         [OperationContract(IsOneWay = true)]
         void Subscriber(string clientID,string clientName);               //訂閱消息

         [OperationContract(IsOneWay = true)]
         void UnSubscriber(string clientID, string clientName);            //取消訂閱
     }


     public interface IPublisherEvents
     {
         [OperationContract(IsOneWay = true)]
         void PublishMessage(string message);                        //發布消息
     }
}
View Code

接口里面只定義了訂閱者(客戶端)調用的訂閱消息和取消訂閱的方法,以及服務端調用客戶端的回調方法PublishMessage,然后我們在FormPublisher.cs里面實現該接口,具體代碼如下:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.ServiceModel;
using System.Threading;

namespace Publisher
{

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]   
    public partial class FormPublisher : Form, IPublisher, IDisposable
    {
        //定義回調客戶端集合
        public static List<IPublisherEvents> ClientCallbackList { get; set; }

        public FormPublisher()
        {
            InitializeComponent();
            ClientCallbackList = new List<IPublisherEvents>();
        }

        //寄宿服務
        private ServiceHost _host = null;
        private void FormPublisher_Load(object sender, EventArgs e)
        {
            _host = new ServiceHost(typeof(Publisher.FormPublisher));
            _host.Open();
            this.label1.Text = "MessageService Opened.";
        }

        //關閉窗體
        private void FormPublisher_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_host != null)
            {
                _host.Close();
                IDisposable host = _host as IDisposable;
                host.Dispose();
            }
        }

        //發布消息
        private void btn_Publish_Click(object sender, EventArgs e)
        {

            var list =Publisher.FormPublisher.ClientCallbackList;
            if (list == null || list.Count == 0)
                return;
            lock (list)
            {
                foreach (var client in list)
                {
                    client.PublishMessage(this.txt_Message.Text);
                }
            }

        }


        //實現訂閱
        public void Subscriber(string clientID, string clientName)
        {
            var client = OperationContext.Current.GetCallbackChannel<IPublisherEvents>();
            var sessionid = OperationContext.Current.SessionId;
            MessageBox.Show( string.Format("客戶端{0} 開始訂閱消息。", clientName));
            OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
            ClientCallbackList.Add(client);

        }


        //取消訂閱
        public void UnSubscriber(string clientID, string clientName)
        {
            var client = OperationContext.Current.GetCallbackChannel<IPublisherEvents>();
            var sessionid = OperationContext.Current.SessionId;
            MessageBox.Show(string.Format("客戶端{0}取消訂閱消息", clientName));
            OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
            ClientCallbackList.Remove(client);
        }


        //關閉通道,移除回調客戶端
        void Channel_Closing(object sender, EventArgs e)
        {
            lock (ClientCallbackList)
            {
                ClientCallbackList.Remove((IPublisherEvents)sender);
            }
        }

    }
}
View Code

注意:當前使用了實例上下文模式為單例模式,我們啟用的是同一個實例上下文模式,即客戶端共享同一個同一個會話,關於實例模式有三種:

1. Single —— 表示所有的客戶端共享一個會話(服務對象)(服務關閉時才會銷毀服務對象)

2. PerCall —— 表示每次調用都會創建一個會話(服務對象)(調用完畢后就會銷毀服務對象)

3. PerSession —— 表示為每個連接(每個客戶端代理對象) 創建一個會話(服務對象),只有指定IsTerminating=true的操作被調用,或者是設定的SessionTimeout超時的時候,服務對象會被銷毀。但支持Session的Binding只有:WSHttpBinding、WSDualHttpBinding、WSFederationHttpBinding、NetTcpBinding。

關於實例上下文模式,我將在后期博文中詳細介紹。

完成后,我們就開始配置我們的服務端的”ABC”,服務端的配置文件如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true"/>
  </system.web>
  <system.serviceModel>

    <services>
      <service name="Publisher.FormPublisher">
        <endpoint address="" binding="netTcpBinding" bindingConfiguration="netTcpExpenseService_ForSupplier" contract="Publisher.IPublisher">
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://172.0.0.1:9999/WcfDuplexService/"/>
            <add baseAddress="http://172.0.0.1:9998/WcfDuplexService"/>
          </baseAddresses>
        </host>
      </service>
    </services>

    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="False"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>


    <bindings>
      <netTcpBinding>
        <binding name="netTcpExpenseService_ForSupplier" closeTimeout="00:01:00"
            openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
            transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
            hostNameComparisonMode="StrongWildcard" listenBacklog="10"
            maxBufferPoolSize="2147483647" maxBufferSize="2147483647" maxConnections="10"
            maxReceivedMessageSize="2147483647">
          <readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="2147483647"
              maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <reliableSession ordered="true" inactivityTimeout="00:10:00"
              enabled="false" />
          <security mode="None">
            <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
            <message clientCredentialType="Windows" />
          </security>
        </binding>
      </netTcpBinding>
    </bindings>

  </system.serviceModel>

</configuration>
View Code

到此我們的發布者(服務端)的代碼完成,編譯后啟動我們的Publisher.exe就可以看到服務寄宿成功的界面如下圖所示:

接下來我們在Subscriber項目中添加服務引用,如下圖所示:

注意:我們選擇http://172.0.0.1:9998/WcfDuplexService地址,因為我們的服務已經采用了元數據地址發布,但是你們在引用的時候把配置文件和引用地址中的172.0.0.1改為

 localhost或者是127.0.0.1,再或者是你們本機的IP。要不然服務地址是不准確的,無法引用。(評論中有人出現了這個問題呢)

接下來我們實現FormSubscriber.cs窗體的代碼:

using System;
using System.Windows.Forms;
using System.ServiceModel;
using System.Threading;
using Subscriber.WcfDuplexService;

namespace Subscriber
{
    public partial class FormSubscriber : Form, IPublisherCallback
    {
        PublisherClient proxy = null;
   
        public FormSubscriber()
        {
             InitializeComponent();
             InstanceContext instance = new InstanceContext(this);
             proxy = new PublisherClient(instance);

             btn_cancle.Enabled = false;
        }

        //實現客戶端回調函數
        public void PublishMessage(string message)
        {
            string msg = string.Format("來自服務端的廣播消息 : {0}",message);
            lst_getMsg.Items.Add(msg);
         }

        //訂閱消息
        private void btn_ok_Click(object sender, EventArgs e)
        {
            btn_ok.Enabled = false;
            btn_cancle.Enabled = true;

            string ClientID = System.Guid.NewGuid().ToString();
            string ClientName = this.textBox1.Text;
            proxy.Subscriber(ClientID, ClientName);              
        }

        //取消訂閱
        private void btn_cancle_Click(object sender, EventArgs e)
        {
            btn_ok.Enabled = true;
            btn_cancle.Enabled = false;

            string ClientID = System.Guid.NewGuid().ToString();
            string ClientName = this.textBox1.Text;
            proxy.UnSubscriber(ClientID, ClientName);
        }
    }
}
View Code

到此,我們整個解決方案已經完成,接下來,我們運行程序來驗證我們需要的結果,首先啟動發布者(即服務端),再啟動訂閱者(即客戶端,注意:這里我們啟動兩個,方便驗證程序效果),運行效果如下:

效果1:client1和client2都訂閱消息,此時兩個客戶端都能收到廣播的消息

效果2:client1訂閱消息和client2取消訂閱,此時只有client1能收到廣播的消息

效果3:client1取消訂閱和client2訂閱消息,此時只有client2收到廣播的消息

 


免責聲明!

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



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