WCF支持服務將調用返回給它的客戶端。在回調期間,許多方面都將顛倒過來:服務將成爲客戶端,客戶端將編程服務。回調操做能夠用在各類場景和應用程序中,但在涉及事件或者服務發生時間須要通知客戶端時,顯得特別有用。編程
回調操做一般被認爲是雙向操做。並不是全部的綁定都支持回調操做,只有在具備了雙向能力的綁定時,才支持回調操做。好比,HTTP協議本質上是與與鏈接無關的,因此他不能用於回調,因此,不能基於BasicHttpBingding綁定或WsHttpBingding綁定使用回調。爲了讓HTTP協議支持回調,WCF提供了WSDualHttpBingding綁定,它實際上設置了兩個HTTP通道:一個用於從客戶端到服務的調用,另外一個則是服務到客戶端的調用。WCF也爲NetTcpBingding和NetNamePipeBingding綁定提供了對回調操做的支持。因此,TCP和IPC協議均支持雙向通訊。安全
雙工回調並非標準的操做,由於沒有對於的行業標準來規定客戶端如何傳遞客戶端地址給服務,或者服務如何實現發佈回調契約。雙工回調會損害WCF的性能。不多用到WSDualHttpBingding,由於它沒法穿越客戶端和服務端的重重阻礙。這種鏈接線問題由Windows Azure AppFabric Service Bus解決了,在雲上它支持雙工對調,使用NetTcpRelayBingding綁定。多線程
1.建立標準契約和回調契約
/// <summary> /// 回調契約 /// </summary> public interface IMyContractCallback { [OperationContract] void OnCallback(string name); } [ServiceContract(CallbackContract = typeof(IMyContractCallback))] public interface IMyContract { [OperationContract] void DoSomething(string name); }2.在服務端實現標準契約
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)] public class MyContract : IMyContract { private IMyContractCallback callback; public void DoSomething(string name) { callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>(); callback.OnCallback(name + "回來了"); } }3.建立服務端承載
App.configclass Program { static void Main(string[] args) { ServiceHost host = new ServiceHost(typeof(WCF.CallbackOperation.Service.MyContract)); host.Open(); Console.WriteLine("服務啓動成功......"); int i = 0; foreach (ServiceEndpoint endpoint in host.Description.Endpoints) { i++; Console.WriteLine("終結點序號:{0},終結點名稱:{1},終結點地址:{2},終結點綁定:{3}{4}", i, endpoint.Name, endpoint.Address, endpoint.Binding, Environment.NewLine); } Console.Read(); } }<system.serviceModel> <services> <service name="WCF.CallbackOperation.Service.MyContract" behaviorConfiguration="CallbackTcpBehavior"> <endpoint name="tcp_IMyContract" address="MyContract" contract="WCF.CallbackOperation.Service.IMyContract" binding="netTcpBinding"></endpoint> <endpoint address="MyContract/mex" binding="mexTcpBinding" contract="IMetadataExchange"></endpoint> <host> <baseAddresses> <add baseAddress="net.tcp://127.0.0.1:9000"/> </baseAddresses> </host> </service> </services> <behaviors> <serviceBehaviors> <behavior name="CallbackTcpBehavior"> <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <diagnostics performanceCounters="All"/> </system.serviceModel>4.實現客戶端代理
使用Visual Studio的SvcUtil工獲取併發
5.在客戶端實現並調用
class Program { static void Main(string[] args) { MyCallback callback = new MyCallback(); callback.CallService(); callback.Dispose(); Console.Read(); } class MyCallback : IMyContractCallback, IDisposable { private MyContractClient m_Proxy; public void CallService() { InstanceContext context = new InstanceContext(this); m_Proxy = new MyContractClient(context); m_Proxy.DoSomething("zxj"); } public void OnCallback(string name) { Console.WriteLine(name); } public void Dispose() { m_Proxy.Close(); } } }實例代碼下載:下載tcp
回調操做是服務契約的一部分,它取決於服務契約對回調契約的定義。一個服務契約最多隻能包含一個回調契約。一旦定義了回調契約,就須要客戶端支持回調,並在每次調用中提供執行服務的回調終結點。若要定義回調契約,則可使用ServiceContract特性提供的Type類型的屬性CallbackContract。函數
public sealed class ServiceContractAttribute : Attribute { public Type CallbackContract { get; set; } }在定義包含回調契約的服務契約時,須要爲ServiceContract提供回調契約的類型,以及回調契約的定義,以下所示:性能
public interface ISomeCallbackContract { [OperationContract] void OnCallback(); } [ServiceContract(CallbackContract=typeof(ISomeCallbackContract))] public interface IMyContract { [OperationContract] void DoSomething(); }注意,回調契約必須標記ServiceContract特性。由於只要類型定義爲回調契約,就意味着它具備ServiceContract特性,而且在服務元數據中也將包含該特性。固然,咱們仍然腰圍全部的回調接口方法標記OperationContract特性。this
當客戶端導入的回調接口與原來的服務端定義的名稱不一樣時,它會被修改成服務契約的接口名後將後綴Callback。例如,若是客戶端導入上個例子的定義,則客戶端會得到如下的定義:spa
/// <summary> /// 回調契約 /// </summary> public interface IMyContractCallback { [OperationContract] void OnCallback(string name); } [ServiceContract(CallbackContract = typeof(IMyContractCallback))] public interface IMyContract { [OperationContract] void DoSomething(string name); }因此,建議在服務端使用命名規範,即在回調契約命名爲服務契約接口後將後綴Callback。線程
客戶端負責託管回調對象以及公開回調終結點。服務實例最內層的執行範圍是實例上下文。InstanceContext類定義的構造函數可以將服務實例傳遞給宿主。
public sealed class InstanceContext : CommunicationObject, IExtensibleObject<InstanceContext> { // 實現服務實例的對象。 public InstanceContext(object implementation); // 爲實例上下文返回服務的實例。 public object GetServiceInstance(); ...... }爲了託管一個回調對象,客戶端須要實例化回調對象,而後經過它建立一個上下文對象:
private MyContractClient m_Proxy; public void CallService() { InstanceContext context = new InstanceContext(this); m_Proxy = new MyContractClient(context); m_Proxy.DoSomething("zxj"); }值得一提的是,雖然回調方法是在客戶端,但它們仍然屬於WCF操做,所以他們都是一個操做調用上下文,經過OperationContext.Current訪問。
雙向代理
不管什麼時候,只要服務終結點的契約定義了一個回調契約,客戶端在與它進行交互時,都必須使用代理建立雙向通訊,並將回調終結點的引用傳遞給服務。所以沒客戶端使用的代理必須繼承一個專門的代理類DuplexClientBase<T>,以下所示:
public abstract class DuplexClientBase<TChannel> : ClientBase<TChannel> where TChannel : class { protected DuplexClientBase(InstanceContext callbackInstance); protected DuplexClientBase(object callbackInstance); protected DuplexClientBase(InstanceContext callbackInstance, ServiceEndpoint endpoint); protected DuplexClientBase(object callbackInstance, ServiceEndpoint endpoint); protected DuplexClientBase(InstanceContext callbackInstance, Binding binding, EndpointAddress remoteAddress); protected DuplexClientBase(object callbackInstance, Binding binding, EndpointAddress remoteAddress); // 獲取內部雙工通道。 public IDuplexContextChannel InnerDuplexChannel { get; } ...... }客戶端須要提供實例上下文綁定給託管宿主的DuplexClientBase<T>構造函數。代理會根據回調上下文構建終結點,從服務終結點配置信息裏推斷回調終結點的信息:回調終結點契約是經過服務契約回調類型定義的。回調終結點會使用與外發調用相同的綁定。對於地址,WCF會使用客戶端機器名。只是簡單地傳遞實例上下文給雙向代理,並使用代理來調用服務暴露的終結點。爲了簡化這個過程,DuplexClientBase<T>提供了能夠接受回調對象的構函數,並將其包裝在上下文裏。不管出於什麼緣由,當客戶端須要訪問上下文時,DuplexClientBase<T>都會另外提供IDuplexContextChannel類型的InnerDuplexChannel屬性。它提供了經過InnerDuplexChannel屬性訪問上下文的方式。
使用Visual Studio可以爲包含了回調契約的目標服務生成代理類,生成的類派生自DuplexClientBase<T>,以下所示:
public partial class MyContractClient : System.ServiceModel.DuplexClientBase<IMyContract>, IMyContract { public MyContractClient(System.ServiceModel.InstanceContext callbackInstance) : base(callbackInstance) { } public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName) : base(callbackInstance, endpointConfigurationName) { } public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) : base(callbackInstance, endpointConfigurationName, remoteAddress) { } public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : base(callbackInstance, endpointConfigurationName, remoteAddress) { } public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : base(callbackInstance, binding, remoteAddress) { } public void DoSomething() { base.Channel.DoSomething(); } public System.Threading.Tasks.Task DoSomethingAsync() { return base.Channel.DoSomethingAsync(); } }客戶端可使用派生的代理類建立回調實例,並將上下文做爲它的宿主,建立代理、調用代理服務,這樣就能夠傳遞迴調終結點的引用。
class MyCallback : IMyContractCallback, IDisposable { private MyContractClient m_Proxy; public void CallService() { InstanceContext context = new InstanceContext(this); m_Proxy = new MyContractClient(context); m_Proxy.DoSomething("zxj"); } public void OnCallback(string name) { Console.WriteLine(name); } }注意,只要客戶端正在等待回調,就不能關閉代理。若是關閉回調終結點,當服務試圖將調用返回時,就會致使服務端產生錯誤。
最多見的實現方式是由客戶端自身實現回調契約,此時,客戶端一般能夠將代理定義爲成員變量,並在釋放客戶端時關閉它,以下所示:
class MyCallback : IMyContractCallback,IDisposable { private MyContractClient m_Proxy; public void CallService() { InstanceContext context = new InstanceContext(this); m_Proxy = new MyContractClient(context); m_Proxy.DoSomething(); } public void OnCallback() { throw new NotImplementedException(); } public void Dispose() { m_Proxy.Close(); } }
隨着服務端的每一次調用,客戶端的回調終結點引用都會被傳遞到服務,組成傳入消息的一部分。OperationContext類爲服務提供了方便訪問回調引用的途徑,即調用泛型方法GetCallbackChannel<T>();
public sealed class OperationContext : IExtensibleObject<OperationContext> { // 獲取調用當前操做的客戶端實例的通道。 public T GetCallbackChannel<T>(); }服務如何處理回調引用及什麼時候決定使用它,徹底由服務選擇,這一點毋庸置疑。服務可以從操做上下文中提取出回調引用,而後將它保存起來以備往後使用;或者能夠在服務運行期間使用它,將調用返回客戶端。以下所示:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] public class MyContract : IMyContract { private IMyContractCallback callback; public void DoSomething(string name) { callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>(); callback.OnCallback(name + "回來了"); } }回調重入
服務可能須要調用傳入的回調引用。然而,這樣的調用在默認狀況下是禁止的,由於它受默認的服務併發管理的限制。在默認狀況下,服務類被配置爲單線程訪問:服務實例上下文與鎖關聯,在任什麼時候刻都只能有一個線程擁有鎖,也只能有一個個線程可以訪問上下文中的服務實例。在操做調用期間,在客戶端發佈的調用須要阻塞服務線程,並調用回調。問題是一旦回調返回它須要的重入的相同上下文及獲取同一個鎖的全部權,處理從相同通道的客戶端返回的應答消息時就會致使死鎖。注意,服務仍然可能調用到其它客戶端的回調,或者調用其餘服務,這屬於正在調用的會致使死鎖的客戶端的回調。
若是單線程的服務實例試圖調用返回給他的客戶端,爲了不死鎖,WCF會拋出InvilidOperationException異常。對於這種狀況,有三種可能的解決方案:
第一種方案是配置服務,容許多線程訪問。因爲服務實例與鎖無關,所以容許正在調用的客戶端回調。可是這種狀況也可能增長服務開發者的負擔,由於它須要爲服務提供同步。
第二種方案是將服務配置爲重入。一旦配置爲重入,服務實例就與鎖管理,同時只容許單線程訪問。若是服務正在回調它的客戶端,WCF就會先釋放鎖。就目前而言,若是服務須要回調它的客戶端,則可使用ServiceBehavior特性的ConcurrencyMode屬性,將服務的併發行爲配置爲多線程或者重入。
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)] public class MyContract : IMyContract { private IMyContractCallback callback; public void DoSomething(string name) { callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>(); callback.OnCallback(name + "回來了"); } }第三種方案是將回調契約操做配置爲單向操做,這樣服務就可以安全地將調用返回給客戶端。由於沒有任何應答消息會競用鎖,即便併發模式設置爲單線程,服務也可以支持回調。注意,實例代碼中使用第二種方案,如使用第三種方案,請從新生成客戶端代理類。
public interface IMyContractCallback { [OperationContract(IsOneWay = true)] void OnCallback(string name); } [ServiceContract(CallbackContract = typeof(IMyContractCallback))] public interface IMyContract { [OperationContract] void DoSomething(string name); } public class MyContract : IMyContract { private IMyContractCallback callback; public void DoSomething(string name) { callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>(); callback.OnCallback(name + "回來了"); } }
若是客戶端保持打開狀態,則服務只能將調用返回給客戶端。一般狀況下,這創建在代理沒有關閉的狀況下。保證代理處於打開狀態一樣能夠避免回調對象被垃圾回收期回收。若是服務維持了一個在對調終極點上的引用,並且客戶端代理是關閉的,或者客戶端應用程序已經推出,那麼當服務調用回調時,就會從服務通道處獲取一個ObjectDisposedException異常。所以,對於客戶端而言,當它再也不須要接收回調會客戶端應用已經關閉,最好可以通知服務無。爲此,能夠在服務契約中顯式添加一個Disconnect()方法。若是每一個放哪廣發調用都擔心回調引用,那麼服務就可以在Disconnect()方法中將回調從內部存儲結構中移除。
固然,也建議開發者同時在服務契約中也顯式提供一個Connect()方法。定義Connect()方法,可以是客戶端重複地鏈接或斷開,同時還提供一個明確的時間點,以判斷什麼時候須要一個回調,由於回調只可以發生在調用Connect()方法以後。
[ServiceContract(CallbackContract = typeof(IMyContractCallback))] public interface IMyContract { [OperationContract] void DoSomething(string name); [OperationContract] void Connect(); [OperationContract] void DisConnect(); } [ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)] public class MyContract : IMyContract { private IMyContractCallback callback; public void DoSomething(string name) { callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>(); callback.OnCallback(name + "回來了"); } public void Connect() { //能夠實現爲回調列表,將回調引用添加到列表中 } public void DisConnect() { //能夠實現爲回調列表,將回調引用從列表中移除 } }鏈接管理與實例操做
單向服務
只有在操做調用回調引用對象自身或者將它存儲在某些全局變量(如靜態變量)時,單調服務纔可以使用 回調引用。既然服務可能使用的存儲在引用對象中的任何實例狀態在操做返回時都會丟失,那麼單調服務就必須使用某些靜態變量用以存儲回調引用。於是,單調服務特別須要相似於Disconnect()方法。若是沒有該放哪廣發,共享存儲就會隨着時間的推移,而出現大量冗餘的回調引用。
單例服務
固然,單例服務也存在相似的狀況。由與單例對象的生命週期不會結束,於是它會無限累計回調引用,隨着時間的推移,回調的客戶端不服存在,大多數回調引用也隨之失效。定義DisConnect()方法能夠保證單例服務只鏈接到對應的活動客戶端。
會話服務
會話服務即便沒有DisConnect()方法也能得到引用,只要它將回調引用保存在某個實例成員的變量中。緣由在於當會話結束時,服務實例會被自動釋放,整個會話不存在保持引用的危險。這就可以保證回調引用老是有效的。可是,若是會話服務爲了讓其它宿主段或跨會話訪問而將它的回調引用保存在某個全局變量中,必須添加DisConnect()方法,已達到移除回調引用的目的,這就是由於在調用Dispose()期間,不能使用回調引用。
咱們也能夠爲會話服務添加Connect()和DisConnect()方法,由於它容許客戶端在會話期間決定什麼時候啓動或中止接受回調消息。
設計回調契約時,須要注意設計上的約束。若是一個服務契約的基契約可回調接口,則服務契約定義的回調結構必須是它的契約定義的全部回調接口的子接口。以下,如下回調契約是無效的:
interface ICallbackContract1 {......} interface ICallbackContract2 {......} [ServiceContract(CallbackContract = typeof(ICallbackContract1))] interface IMyBaseContract {......} //無效 [ServiceContract(CallbackContract = typeof(ICallbackContract2))] interface IMySubContract {......}IMySubContract不能指定ICanllbackContract2爲它的回調契約,由於ICallbackContract2不能ICallbackContract1的子類,而IMyBaseContract(IMySubContract的父接口)定義了ICallbackContract1做爲它的回調接口。
知足約束的最直接方法實在回調契約層級反映服務契約的層次:
interface ICallbackContract1 {......} interface ICallbackContract2:ICallbackContract1 {......} [ServiceContract(CallbackContract = typeof(ICallbackContract1))] interface IMyBaseContract {......} [ServiceContract(CallbackContract = typeof(ICallbackContract2))] interface IMySubContract {......}