除了從Client呼叫WCF服務取得結果,WCF也支援Server端反過來呼叫寫在Client端的方法(類似事件觸發概念),這種雙工(Duplex)模式算是WCF的一大賣點。Web API要實現類似概念得靠SignalR架構支援,直接內建雙工模式的WCF略勝一籌。

這篇文章,我們就來建立一個簡單的WCF雙工服務,實際體驗它的威力。

假設我們有一個ITimer報時服務,構想是在WCF Server跑一個PerSession Instance(關於InstanceContextMode.PerSession的意義請參考前文),當Client呼叫ITimer.Start(),ITimer服務就每隔一秒呼叫Client端的OnTick()方法,提供當前Server時間字串,直到Client呼叫Stop()為止。

擬訂好構想,實際在Visual Studion建立WCF Service專案,先定義ITimer,跟以前不同的是,ITimer需宣告SessionMode.Required指定Service Instance開啟Session模式,另外宣告CallbackContract=typeof(ITimerCallback),指定Client端必須實作ITimerCallback介面供Server呼叫。

using System.ServiceModel;
 
namespace WcfWas
{
    [ServiceContract(
        SessionMode=SessionMode.Required, 
        CallbackContract=typeof(ITimerCallback))]
    public interface ITimer
    {
        [OperationContract]
        void Start();
        [OperationContract]
        void Stop();
    }
 
    public interface ITimerCallback 
    {
        [OperationContract(IsOneWay = true)]
        void OnTick(string time);
    }
}

ITimer.svc.cs長這樣:

using System;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;
 
namespace WcfWas
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
    public class Timer : ITimer, IDisposable
    {
        private Task task = null;
        ITimerCallback Callback = null;
        CancellationTokenSource canTknSrc = new CancellationTokenSource();
 
        public void Start()
        {
            //透過OperationContext.Current取得Callback
            Callback = 
                OperationContext.Current.GetCallbackChannel<ITimerCallback>();
            //使用CancellationToken控制Task執行何時結束
            var canTkn = canTknSrc.Token;
            task = Task.Factory.StartNew(() =>
            {
                while (!canTkn.IsCancellationRequested)
                {
                    Thread.Sleep(1000);
                    if (canTkn.IsCancellationRequested) break;
                    if (Callback != null)
                    {
                        Callback.OnTick(
                            DateTime.Now.ToString("HH:mm:ss.fff"));
                    }
                }
            }, canTkn);
        }
 
        public void Stop()
        {
            if (task != null)
            {
                canTknSrc.Cancel();
                task.Wait();
                task = null;
            }
        }
 
        public void Dispose()
        {
            Stop();
        }
    }
}

有幾個重點:

  1. 使用InstanceContextMode.PerSession。
  2. Start()由OperationContext.Current.GetCallbackChannel<ITimerCallback>()與Client寫的OnTick()事件搭上線。
  3. 每秒送時間回Client端的工作由Task.Factory.StartNew()另起一個執行緒處理,並用CancellationToken控制結束時機。(延伸閱讀:簡介.NET 4.0的多工執行利器--Task
  4. Stop()設定CancellationToken,並等待Task結束。
  5. 實作IDispose(),若Client端未呼叫Stop(),Instance銷毁前要強制結束作業。

要使用雙工服務時,必須選用支援雙工及Session的Binding(參考:WCF預設Binding),例如WsDualHttpBinding、NetTcpBinding,由於這些通訊協定預設啟用Windows認證會造成跨機器溝通的困擾,故修改web.config設定如下:

    <bindings>
      <netTcpBinding>
        <binding name="NoneSecurityNetTcpBinding">
          <security mode="None"></security>
        </binding>
      </netTcpBinding>
      <wsDualHttpBinding>
        <binding name="NoneSecurityWsDualHttpBinding">
          <security mode="None"></security>
        </binding>
      </wsDualHttpBinding>
    </bindings>
    <services>
      <service name="WcfWas.Timer">
        <endpoint address="" binding="wsDualHttpBinding" contract="WcfWas.ITimer"
                  bindingConfiguration="NoneSecurityWsDualHttpBinding"></endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        <endpoint address="" binding="netTcpBinding" contract="WcfWas.ITimer"
                  bindingConfiguration="NoneSecurityNetTcpBinding"></endpoint>
      </service>
    </services>

將程式部署到IIS上(IIS才支援net.tcp),在WCF Client加入參照(記得要加上<security mode="None" />),使用以下程式進行測試:

        //宣告一個類別實作ITimerCallback介面
        public class OnTickHandler : WcfDuplex.ITimerCallback
        {
            public void OnTick(string time)
            {
                Console.WriteLine("Tick - " + time);
            }
        }
 
        static void Main(string[] args)
        {
            //傳入OnTickHandler物件,建立InstanceContext
            InstanceContext ctx = new InstanceContext(new OnTickHandler());
            //使用InstanceContext建構WCF Client物件
            WcfDuplex.TimerClient tc =
                new WcfDuplex.TimerClient(ctx, "WSDualHttpBinding_ITimer");
                //new WcfDuplex.TimerClient(ctx, "NetTcpBinding_ITimer");
            tc.Start();
            Console.WriteLine("WCFClient Start Timer");
            //等待10秒
            Thread.Sleep(10000);
            tc.Stop();
            Console.WriteLine("WCFClient Stop Timer");
            //等待五秒,確認不再有Callback
            Thread.Sleep(5000);
            Console.WriteLine("Test Done");
            Console.Read();
        }

雙工服務的Client寫法跟以前有些不同,為了讓Server反向呼叫Client端,需定義一個Callback Handler類別(即程式裡的OnTickHandler)實作ITimerCallback.OnTick。而建立TimerClient之前,要以OnTickHandler物件作為參數建構InstanceContext物件,再以此InstanceContext物件當參數建構TimerClient。如此,從Server端呼叫OperationContext.Current.GetCallbackChannel時才能跟Client端寫的OnTick()方法連結在一起。由於WCF服務同時支援WsDualHttpBinding及NetTcpBinding,故建構時需傳入Endpoint名稱指定通訊管道(參考前文)。

Start()後程式Thread.Sleep()等待10秒,這段期間,Server將每秒一次主動呼叫Client端的OnTick()方法印出當前時間。10秒後,Client呼叫Stop()並靜待5秒,確認Server不再觸發OnTick,種式結束。

測試成功,我也會寫WCF雙工服務囉!


Comments

Be the first to post a comment

Post a comment