除了從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))]
publicinterface ITimer
{
[OperationContract]
void Start();
[OperationContract]
void Stop();
}
publicinterface 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)]
publicclass Timer : ITimer, IDisposable
{
private Task task = null;
ITimerCallback Callback = null;
CancellationTokenSource canTknSrc = new CancellationTokenSource();
publicvoid 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);
}
publicvoid Stop()
{
if (task != null)
{
canTknSrc.Cancel();
task.Wait();
task = null;
}
}
publicvoid Dispose()
{
Stop();
}
}
}
有幾個重點:
- 使用InstanceContextMode.PerSession。
- Start()由OperationContext.Current.GetCallbackChannel<ITimerCallback>()與Client寫的OnTick()事件搭上線。
- 每秒送時間回Client端的工作由Task.Factory.StartNew()另起一個執行緒處理,並用CancellationToken控制結束時機。(延伸閱讀:簡介.NET 4.0的多工執行利器--Task)
- Stop()設定CancellationToken,並等待Task結束。
- 實作IDispose(),若Client端未呼叫Stop(),Instance銷毁前要強制結束作業。
要使用雙工服務時,必須選用支援雙工及Session的Binding(參考:WCF預設Binding),例如WsDualHttpBinding、NetTcpBinding,由於這些通訊協定預設啟用Windows認證會造成跨機器溝通的困擾,故修改web.config設定如下:
<bindings>
<netTcpBinding>
<bindingname="NoneSecurityNetTcpBinding">
<securitymode="None"></security>
</binding>
</netTcpBinding>
<wsDualHttpBinding>
<bindingname="NoneSecurityWsDualHttpBinding">
<securitymode="None"></security>
</binding>
</wsDualHttpBinding>
</bindings>
<services>
<servicename="WcfWas.Timer">
<endpointaddress=""binding="wsDualHttpBinding"contract="WcfWas.ITimer"
bindingConfiguration="NoneSecurityWsDualHttpBinding"></endpoint>
<endpointaddress="mex"binding="mexHttpBinding"contract="IMetadataExchange"/>
<endpointaddress=""binding="netTcpBinding"contract="WcfWas.ITimer"
bindingConfiguration="NoneSecurityNetTcpBinding"></endpoint>
</service>
</services>
將程式部署到IIS上(IIS才支援net.tcp),在WCF Client加入參照(記得要加上<security mode="None" />),使用以下程式進行測試:
//宣告一個類別實作ITimerCallback介面
publicclass OnTickHandler : WcfDuplex.ITimerCallback
{
publicvoid OnTick(string time)
{
Console.WriteLine("Tick - " + time);
}
}
staticvoid 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雙工服務囉!