昨天寫了一篇Remoting中如何實現雙向通信的文章《[原創].NET Remoting: 如何通過Remoting實現雙向通信(Bidirectional Communication) 》,作為對比,今天我們來討論一下WCF的雙向通信。
為了使我們能夠更好地對比雙向通信在Remoting中和WCF中的實現,我們的Sample采用一樣的業務邏輯——調用一個數學計算的遠程調用,除了傳遞相應的操作數之外,我們還傳遞一個對象,這個對象可以在Server端中回調 (Callback) 把運算結果在Client端顯示出來。
Step1:構建整個Solution的整體構架。
整個Solution的架構在我的之前的Blog有了詳細的介紹([原創]我的WCF之旅(1):創建一個簡單的WCF程序),這裡只作一個簡單的介紹。
Artech.WCFService.Contract: Class Library Project,用來保存Contract(Service Contact、Message Contract、Data Contract), 之所以把Contract獨立出來的原因是考慮到他同時被Server端——Service本身和Service Hosting和Client端使用
Artech.WCFService.Service:Class Library Project,Service的業務邏輯, 這個Project引用Artech.WCFService.Contract Project和System.ServiceModel DLL。
Artech.WCFService.Hosting:Console Application, 用於以Self-Hosting的方式Host Service。這個Project引用Artech.WCFService.Contract和Artech. Project WCFService.Service。Project和System.ServiceModel DLL。
Artech.WCFService.Client:Console Application, 用以模擬現實中的調用Service的Clinet。這個Project引用Artech.WCFService.Contract Project 和System.ServiceModel DLL。
http://localhost/WCFService: Web Site Project, 用於模擬如何把Service Host到IIS中。這個Project引用Artech.WCFService.Contract、Artech.WCFService.Service和System.ServiceModel DLL。
Step 2 在Artech.WCFService.Contract定義Calculator Service 和Callback的Contract
1.IDuplexCalculator.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace Artech.WCFService.Contract
{
[ServiceContract(CallbackContract = typeof(ICalculatorCallback))]
public interface IDuplexCalculator
{
[OperationContract]
void Add(double x, double y);
}
}
2.ICalculatorCallback.cs using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace Artech.WCFService.Contract
{
[ServiceContract]
public interface ICalculatorCallback
{
[OperationContract]
void ShowResult(double x, double y, double result);
}
}
這裡有以下幾點需要注意的:
1.在一個分布式的環境中,Client能夠調用Service,它必須知道Service的Contract, Contract定義了Service暴露給外界的所有可用的Operation,以及這些Operation的簽名(Signature).至於Service中定義的Opertion采用怎樣的實現,Client不需要了解。這也是在WCF中把Service Contract與具體的Service Implementation相互分離的一個重要原因——我們把Contract單獨提取出來,把他暴露給Client,從而可以避免把過多的暴露業務邏輯的實現。
2.在一個分布式的環境中,Serer端和Client並不是一成不變的,他們是可以相互轉化的。提供服務的就是Server,消費Service的就是Client。在這個例子中,當Artech.WCFService.Client調用Host在Artech.WCFService.Hosting中的DuplexCalculatorService(定義在 Artech.WCFService.Service中),Artech.WCFService.Client是Client,而Server端的執行環境是Artech.WCFService.Hosting。而當Calculator Service回調(Callback)Client的邏輯把運算結果顯示出來時候,因為Callback的邏輯是在Artech.WCFService.Client中執行的,所以Artech.WCFService.Client成了Server,而CalculatorCallbackHandler(定義在 Artech.WCFService.Client中)成了真正的Service。
3.我們已經說過Client能夠調用Service,它必須知道Service的Contract。所以DuplexCalculatorService能過Callback Artech.WCFService.Client,它也必須知道回調操作的Contract。WCF通過在ServiceContractAttribute中的CallbackContrac參數在制定。
[ServiceContract(CallbackContract = typeof(ICalculatorCallback))]
public interface IDuplexCalculator
{
[OperationContract]
void Add(double x, double y);
}
Step 3 在Artech.WCFService.Service定義Duplex Calculator Service
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class DuplexCalculatorService:IDuplexCalculator
{
IDuplexCalculator Members#region IDuplexCalculator Members
public void Add(double x, double y)
{
double result = x + y;
ICalculatorCallback callback = OperationContext.Current.GetCallbackChannel<ICalculatorCallback>();
callback.ShowResult(x, y, result);
}
#endregion
}
這裡有以下幾點需要注意的:
1.必須把並發模式ConcurrencyMode設置為ConcurencyMode. Reentrant或者ConcurencyMode.Multiple。要弄清種種的原因,我們先來看看在本例中的具體的消息交互的情況(假設我們的調用Duplex Calculator Service 和回調都采用Request/Response的MessageExcahnge Pattern,時間上一般這種情況我們應該采用One-way的ME):
首先Client調用Duplex CalculatorService, Service Request從Client到Service,Service開始執行運算,運算完成後Callback Client將運算結構在Client端顯示出來,這個過程中Service向Client發送一個Callback Message,等Client完成Callback操作後,會向Service端發送一個Callback Response(實際上是一個空的Message——以為Callback操作沒有返回值),Service收到Callback Response之後,會執行後續的操作,等所有的操作執行完畢,會發送ServiceResponse(這裡也是一個空的Message)到Client。
現在我們 來看看為什麼在建立DuplexService的時候要把並發模式設為ConcurencyMode. Reentrant或者ConcurencyMode.Multiple。在默認的並發模式下(ConcurencyMode.Single),WCF為了保證其線程安全性(ThreadSafety),在整個調用Service的過程中,InstanceContext會被WCF鎖住(Lock)。一本Sample為例,從Client向Service發送Service Request 到手的Server發回的Service Resqonse,整個InstanceContext會在Server端被鎖住, 由於在Client執行的Callback操作使用的是同一個InstanceContext, 這樣形成了一個死鎖(DeadLock)——Calculator Service必須等Callback操作完成之後才能執行後續的操作,而Callback操作必須等待InstanceContext被解鎖(Unlock)才能執行,然而InstanceContext卻被Calculator Service鎖住。
當ConcurencyMode為ConcurencyMode. Reentrant或者ConcurencyMode.Multiple的時候。當Serivice向外調用某個操作(outgoing call)的時候,或者說在向外發送一個Outgoing Message的時候,WCF會解鎖(Unlock)InstanceContext。以本例為例,Service 回調Client的時候,原來被鎖住的InstanceContext會被解鎖。這樣Callback操作就可以利用InstanceContext來執行。
2.Service可以通過OperationContext.Current.GetCallbackChannel<T>() 來或者Client在調用Calculator Service時指定的Callback Object。其中T一般被指定為Callback Contract對應的Type。
ICalculatorCallback callback = OperationContext.Current.GetCallbackChannel<ICalculatorCallback>();
Step 4 在Artech.WCFService.Hosting中Host Duplex Calculator Service
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="calculatorServieBehavior">
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="calculatorServieBehavior" name="Artech.WCFService.Service.DuplexCalculatorService">
<endpoint binding="wsDualHttpBinding" contract="Artech.WCFService.Contract.IDuplexCalculator">
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost:7777/DuplexCalculator" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
Program.cs
using System;
這裡需要注意的時候,在Host Duplex Calculator Service 的時候,我們要為它添加相應的Endpoint。對於支持雙向通信的Service,它對Endpoint有一定的要求——我們必須為它指定一個支持Duplex MEP(Message Exchange Pattern)的Binding——比如wsDualHttpBinding,netDualTcpBinding。這裡我們使用的時wsDualHttpBinding。
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using Artech.WCFService.Contract;
using Artech.WCFService.Service;
using System.ServiceModel.Description;
namespace Artech.WCFService.Hosting
{
class Program
{
static void Main(string[] args)
{
HostDuplexCalculator();
}
static void HostDuplexCalculator()
{
using (ServiceHost calculatorSerivceHost = new ServiceHost(typeof(DuplexCalculatorService)))
{
calculatorSerivceHost.Opened += delegate
{
Console.WriteLine("Duplex calculator Service has begun to listen ");
};
calculatorSerivceHost.Open();
Console.Read();
}
}
}
}
Step 5 在Artech.WCFService.Client定義Callback對象和調用Duplex Calculator Service
DuplexCalculatorClient.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using Artech.WCFService.Contract;
namespace Artech.WCFService.Client
{
class DuplexCalculatorClient:ClientBase<IDuplexCalculator>,IDuplexCalculator
{
public DuplexCalculatorClient(InstanceContext callbackInstance)
: base(callbackInstance)
{ }
IDuplexCalculator Members#region IDuplexCalculator Members
public void Add(double x, double y)
{
this.Channel.Add(x, y);
}
#endregion
}
}
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<wsDualHttpBinding>
<binding name="wsDualBinding_IDuplexCalculator" clientBaseAddress="http://localhost:6666/myClient/" />
</wsDualHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:7777/DuplexCalculator" binding="wsDualHttpBinding"
bindingConfiguration="wsDualBinding_IDuplexCalculator" contract="Artech.WCFService.Contract.IDuplexCalculator"
name="duplexCalculatorEndpoint" />
<endpoint address="http://localhost/WCFService/SessionfulCalculatorService.svc"
binding="wsHttpBinding" bindingConfiguration="" contract="Artech.WCFService.Contract.ISessionfulCalculator" />
</client>
</system.serviceModel>
</configuration>
CalculatorCallbackHandler.cs
using System;
using System.Collections.Generic;
using System.Text;
using Artech.WCFService.Contract;
namespace Artech.WCFService.Client
{
class CalculatorCallbackHandler:ICalculatorCallback
{
ICalculatorCallback Members#region ICalculatorCallback Members
public void ShowResult(double x, double y, double result)
{
Console.WriteLine("x + y = {2} where x = {0} and y = {1}", x, y, result);
}
#endregion
}
}
Program.cs
using System;
這裡有以下幾點需要注意的:
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Artech.WCFService.Contract;
namespace Artech.WCFService.Client
{
class Program
{
static void Main()
{
try
{ InvocateDuplexCalculator();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.Read();
}
static void InvocateDuplexCalculator()
{
CalculatorCallbackHandler callbackHandler = new CalculatorCallbackHandler();
using(DuplexCalculatorClient calculator = new DuplexCalculatorClient(new InstanceContext(callbackHandler)))
{
Console.WriteLine("Begin to invocate duplex calculator ");
calculator.Add(1, 2);
}
}
}
}
1.在調用Duplex Calculator Service的時候,我們需要指定執行回調的Callback對象。在WCF中,Callback對象用一個InstanceContext對象來表示。而他在DuplexCalculatorClient的構造函數中指定。
public DuplexCalculatorClient(InstanceContext callbackInstance)
: base(callbackInstance)
{ }
2.Client調用Duplex Calculator Service,Service端需要注冊相應的Channel來監聽來自Client的請求。同理,Service回調Client,Client也需要相應的Channel來監聽來自Service的回調。這個Channel通過下面的方式注冊。
<wsDualHttpBinding>
<binding name="wsDualBinding_IDuplexCalculator" clientBaseAddress="http://localhost:6666/myClient/" />
</wsDualHttpBinding>
到現在為止我們已經完成了所有的Program,我們來運行一下。
1.運行Artech.DuplexRemoting.Hosting
2.運行Artech. WCFService.Client
將Duplex Calculator Service Host 到IIS中
1.在http://localhost/WCFService中添加於Artech.WCFService.Service。DuplexCalculatorService相對應的SVC文件。
DuplexCalculatorService.svc
<%@ ServiceHost Language="C#" Debug="true" Service="Artech.WCFService.Service.DuplexCalculatorService" %>
2.並添加類似於Artech.WCFService.Hosting/App.Config中 的Configuration。
<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="calculatorServiceBehavior">
<serviceMetadata httpGetEnabled="true" ></serviceMetadata>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="calculatorServiceBehavior" name="Artech.WCFService.Service.DuplexCalculatorService">
<endpoint binding="wsDualHttpBinding" contract="Artech.WCFService.Contract.IDuplexCalculator" />
</service>
</services>
</system.serviceModel>
<system.web>
<compilation debug="true">
<assemblies>
<add assembly="System.Security, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="Microsoft.Transactions.Bridge, Version=3.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="SMDiagnostics, Version=3.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.IdentityModel.Selectors, Version=3.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="System.Web.RegularExpressions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Messaging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="System.ServiceProcess, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/></assemblies></compilation>
</system.web>
</configuration>
這樣我們可以不需要Hosting的情況下通過這樣的Uri訪問Duplex Calculator Service:http://localhost/Artech.WCFService/ DuplexCalculatorService.svc。
3.修改Client的App.Config——修正Endpoint 的Address:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<wsDualHttpBinding>
<binding name="wsDualBinding_IDuplexCalculator" clientBaseAddress="http://localhost:6666/myClient/" />
</wsDualHttpBinding>
</bindings>
<client>
<endpoint address=" http://localhost/Artech.WCFService/ DuplexCalculatorService.svc " binding="wsDualHttpBinding"
bindingConfiguration="wsDualBinding_IDuplexCalculator" contract="Artech.WCFService.Contract.IDuplexCalculator"
name="duplexCalculatorEndpoint" />
<endpoint address="http://localhost/WCFService/SessionfulCalculatorService.svc"
binding="wsHttpBinding" bindingConfiguration="" contract="Artech.WCFService.Contract.ISessionfulCalculator" />
</client>
</system.serviceModel>
</configuration>在不起用Hosting的情況下運行Artech.WCFService.Client,我們一樣可以得到相同的結果。
本文配套源碼