閱讀596 返回首頁    go 阿裏雲 go 技術社區[雲棲]


WCF中的Binding模型之二: 信道與信道棧(Channel and Channel Stack)

WCF采用基於消息交換的通信方式,而綁定則實現了所有的通信細節。綁定通過創建信道棧實現了消息的編碼與傳輸,以及對WS-*協議的實現。在這一節中,我們就來著重介紹WCF中的信道和信道棧。在正式開始對信道和信息棧的介紹之前,我們先來介紹兩個重要的類型:CommunicationObject和DefaultCommunicationTimeouts。

一、 CommunicationObject與DefaultCommunicationTimeouts

WCF綁定模型涉及多種類型的組件,比如信道、信道監聽器、信道工廠等等。從功能上講,這些對象都是為通信服務的,我們可以把它們稱為通信對象(Communication Object)。對於這些通信對象來說,在通信不同的階段,它們往往具有不同的狀態;從整個通信的生命周期來看,在不同階段過渡的過程中,它們具有一些相似的狀態轉換方式。

WCF定義了一個特殊的接口,System.ServiceModel.ICommunicationObject,來管理通信對象的狀態和狀態的轉換。下麵是ICommunicationObject的定義:

   1: public interface ICommunicationObject
   2: {
   3:     // Events
   4:     event EventHandler Closed;
   5:     event EventHandler Closing;
   6:     event EventHandler Faulted;
   7:     event EventHandler Opened;
   8:     event EventHandler Opening;
   9:  
  10:     // Methods
  11:     void Open();
  12:     void Open(TimeSpan timeout);
  13:     IAsyncResult BeginOpen(AsyncCallback callback, object state);
  14:     IAsyncResult BeginOpen(TimeSpan timeout, AsyncCallback callback, object state);
  15:     void EndOpen(IAsyncResult result);
  16:  
  17:     void Close();
  18:     void Close(TimeSpan timeout);
  19:     IAsyncResult BeginClose(AsyncCallback callback, object state);
  20:     IAsyncResult BeginClose(TimeSpan timeout, AsyncCallback callback, object state);
  21:     void EndClose(IAsyncResult result);
  22:  
  23:     void Abort();
  24:  
  25:     // Properties
  26:     CommunicationState State { get; }
  27: }

 

ICommunicationObject的State屬性,表示通信對象當前所處的狀態。該屬性通過一個名為System.ServiceModel.CommunicationState的枚舉類型表示,通信對象典型的六種狀態都定義在CommunicationState中:被創建(Created)、正被開啟(Opening)、已經被開啟(Opened)、正被關閉(Closing)、已經被關閉(Closed)已經出錯(Faulted)。

   1: public enum CommunicationState
   2: {
   3:     Created,
   4:     Opening,
   5:     Opened,
   6:     Closing,
   7:     Closed,
   8:     Faulted
   9: }

 

ICommunicationObject定義了以下三種類型的成員:

 

  • 事件:當正在進行狀態轉化,或者是狀態轉換成功,會觸發相應的事件。通過注冊相應的事件,可以在某個狀態轉換環節中注入你需要的處理操作。
  • 方法:定義了三種類型的操作:開啟(open)、關閉(close)、中止(abort)。關於“關閉”和“中止”在功能上具有相似之出,都是斷開連接、回收對象。不過它們具有不同之處,很多英文文章或書籍將“關閉(close)”成為“graceful shutdown(優雅地關閉)”,而將“中止(abort)”描述為“immediate shutdown(立即關閉)”。那我們關閉電腦來說,前麵一種是通過操作係統進行關閉,後一種則是直接切斷電源。對於前一種方式,在關閉過程中,會進行一些IO操作。
  • 屬性:在上麵已經提到,屬性State代表通信對象當前所處的狀態。

由於WCF處理的是跨應用程序域(Application Domain)、跨機器甚至是跨網絡的通信。所以WCF服務調用的大部分時間都在進行象網絡傳輸這樣的IO操作,對於這種IO綁定(IO bound)的操作,對於多線程、異步的考慮肯定是可以不免的,所以ICommunicationObject中的開啟和關閉操作,既定義了一個的同步方法,也按照異步編程模型(APM:Asynchronous Programming Mode)定義了異步方法。

除了簡單定義ICommunicationObject接口之外,WCF還定義了一個實現了該接口的基類:System.ServiceModel.Channels.CommunicationObject。

   1: public abstract class CommunicationObject : ICommunicationObject 
   2: { 
   3:         //... ...
   4: } 

 

在WCF體係中,很多的基於通信的基類都繼承自CommunicationObject,比如信道的基類System.ServiceModel.Channels.ChannelBase;信道工廠和信道監聽器的基類System.ServiceModel.Channels.ChannelManagerBase;ServiceHost的基類System.ServiceModel.ServiceHostBase;信道分發器的基類System.ServiceModel.Dispatcher.ChannelDispatcherBase;等等。大體的繼承結構如圖1所示 的類圖所示。

image

圖1 CommunicationObject繼承關係

由於WCF往往需要跨域網絡進行服務的訪問,較之一般的方法調用,服務訪問的所花的時間往往較長,所以對超時的處理顯得異常重要。比如對於消息的發送,可能由於網絡的故障,該消息在一端時間內根本無法成功發送,客戶端程序不可能無限製地等待下去。一般的情況下,我們會設定一個操作執行的所允許的最大時限,一旦超時則取消操作,並進行相應的超時處理。

我們回顧一下ICommunicationObject的Open和BeginOpen方法,我們會發現它們各有兩個重載,其中一個具有的TimeSpan類型的timeout參數,另一個則沒有。在這裏的timeout參數實際上代表Open方法執行的超時時間,如果Open操作執行的時間過長,一旦超過了該事件,操作將被立即中止。

   1: public interface ICommunicationObject 
   2: { 
   3:     void Open(); 
   4:     void Open(TimeSpan timeout); 
   5:     IAsyncResult BeginOpen(AsyncCallback callback, object state); 
   6:     IAsyncResult BeginOpen(TimeSpan timeout, AsyncCallback callback, object state); 
   7:     //... ... 
   8: }

可能讀者會問,對於沒有timeout參數的操作,比如無參的Open方法,是否意味著沒有這樣的超時限製,操作將會一直執行下去直到操作正常結束呢?答案是否定的,實際上,對於沒有顯式指定超時時限的操作,采用的是默認的超時時限。WCF為所有需要默認超時時限的通信對象定義了一個接口:System.ServiceModel.IDefaultCommunicationTimeouts。在IDefaultCommunicationTimeouts中定一個了四個Timeout屬性,分別定義了開啟、關閉、發送、接收四大操作的超時時限。

   1: public interface IDefaultCommunicationTimeouts
   2: {
   3:     // Properties 
   4:     TimeSpan CloseTimeout { get; }
   5:     TimeSpan OpenTimeout { get; }
   6:     TimeSpan ReceiveTimeout { get; }
   7:     TimeSpan SendTimeout { get; }
   8: }

很多的基於通信的基類都實現了IDefaultCommunicationTimeouts接口,比如信道的基類System.ServiceModel.Channels.ChannelBase信道工廠和信道監聽器的基類System.ServiceModel.Channels.ChannelManagerBase;以及所有綁定對象的基類System.ServiceModel.Channels.Binding等等。

二、 IChannel和ChannelBase

WCF中信道層中的每種類型的信道直接或者間接實現了接口System.ServiceModel.Channels.IChannel,IChannel的定義異常簡單,僅僅具有一個唯一範型方法成員:GetProperty<T>()

   1: public interface IChannel : ICommunicationObject
   2: {
   3:     // Methods 
   4:     T GetProperty<T>() where T : class;
   5: }
   6:  

通過調用信道對象GetProperty<T>方法,獲得具有範型類型的屬性。這個方法比較重要,因為它是探測信道是否具有某種能力的一種有效的方法。比如我們可以通過該方法,指定相應的範型類型,確定信道是否支持某種Channel Shape(關於channel shape將在接下來的部分中進行介紹),消息版本和安全模式等等。

除了IChannel接口之外,WCF還定義了一個實現了IChannel接口的基類:System.ServiceModel.Channels.ChannelBase。。除了實現了IChannel接口,ChannelBase還實現了另外兩個接口:ICommnucationObject和IDefaultCommunicationTimeouts,並直接繼承自CommnucationObject。

   1: public abstract class ChannelBase : CommunicationObject, IChannel, ICommunicationObject, IDefaultCommunicationTimeouts
   2: {
   3:     public virtual T GetProperty<T>() where T : class;
   4:     //... ... 
   5:     TimeSpan IDefaultCommunicationTimeouts.CloseTimeout { get; }
   6:     TimeSpan IDefaultCommunicationTimeouts.OpenTimeout { get; }
   7:     TimeSpan IDefaultCommunicationTimeouts.ReceiveTimeout { get; }
   8:     TimeSpan IDefaultCommunicationTimeouts.SendTimeout { get; }
   9: }

WCF完全采用基於消息的通信方式,對服務的消費最終通過一些列的消息交換實現。WCF應用在不同的場景中按照不同的模式進行消息交換。

3.1. 消息交換模式(MEP)

消息交換模式(Message Exchange Pattern:MEP)在SOA中是一個重要的概念。在W3C的文獻中對MEP的官方定義是這樣的:MEP定義了參與者進行消息交換的模板(原文是:a template that describes the message exchange between messaging participants.),這是一個很抽象的定義。實際上我們可以這樣來理解MEP:消息交換模式(MEP)代表一係列的模板,它們定義了消息的發送者和接收者相互進行消息傳輸的次序。比較典型的消息交換模式包含以下三種:數據報模式(Datagram)、請求/回複模式(Request/Reply)以及雙工模式(Duplex)。

數據報模式(Datagram

數據報模式是最簡單的消息交換模式,又稱為發送/遺忘(Send/Forget)或者是單向模式(One-way)。數據報模式基於從一個源到一個或者多個目的地的單向消息傳輸。如圖2所示,在數據報模式下,消息的發送方將消息發送到接收方,並不希望收到對象的回複。

image

圖2數據報消息交換模式

數據報模式具有一些變形,比較典型的包括以下一些消息交換的方式:

  • 單目的地模式:一個消息的發送方將消息發送給單一的接收方
  • 多投模式:一個消息發送方將消息發送給一係列預定義的接收方
  • 廣播模式:和多投模式相似,隻是接收方的範圍更加寬泛

數據報模式一般采用異步的消息發送方式,並不希望接收到對方的回複消息,在個別情況下甚至不關心消息能否正常地被接收。

請求/回複模式(Request/Reply

在這三種典型的消息交換模式中,請求/回複模式是使用得最多的一種模式。在這種模式下,消息發送方來將消息發送給接收方後會等待對方的回複。請求/回複模式的消息交換情況如下圖所示。請求/回複模式一般采用同步的通信模式(盡管該模式也可以用於異步通信)。

image

圖3 請求-回複消息交換模式

雙工模式(Duplex

如果采用雙工的消息交換模式,在進行消息交換過程中,任何一方都可以向對方發送消息,如圖4所示。

image

圖4雙工消息交換模式

雙工通信使服務端回調客戶端成為可能:客戶端在調用服務的時候,指定一個回調對象,服務端操作執行過程中可以通過回調對象回調客戶端的操作。比較典型雙工通信是我們熟悉的訂閱/發布模式。訂閱/發布模式下的消息交換雙方的角色發生了變化,傳統的發送方和接收方變成了訂閱方和發布方。訂閱方向發布方發送訂閱消息定於某一主題進行訂閱,發布方接收到訂閱消息後將訂閱方添加到訂閱列表之中。主題發布的時候,發布方提取當前主題的所有訂閱方,對它們進行消息廣播。

由於消息的交換依賴於網絡傳遞,所以消息交換模式與網絡協議的支持是一個不得不考慮的。對於雙工通信模式來說,它對於基於TCP協議的通信來說是完全沒有問題,因為TCP協議本身就是全雙工的網絡通信協議。但是對於HTTP來說,它本身就是簡單的基於請求/回複的網絡協議,是不支持雙工通信的。WCF通過WsDualHttpBinding實現了基於HTTP協議的雙工通信,實際上是采用了兩個HTTP通道實現的。

3.2. Channel Shape

在上麵我們討論了三種典型的消息交換模式(MEP),現在我們結合MEP再來討論我們本節的主題:信道與信道棧。信道棧是消息交換的管道,在不同的消息交換模式下,這個管道在消息的發送端和接收端扮演著不同的角色。在數據報模式下,發送端的信道棧的作用是輸出(Output)數據報,接收端則是輸入(Input)數據報;對於請求恢複模式來說,發送端的作用是發送消息請求(Request),而接收端則是恢複(Reply)請求;而在雙工通信模式下,消息交換的雙方的地位完全是等價的,它們都具有 輸出和輸入的功能。

WCF通過一個特殊的術語來表述不同的消息交換模式對消息交換雙方信道的不同要求:Channel Shape。Channel Shape按照適用的消息交換模式的不同,將信道進行了分類。WCF為這些信道定義了一些列的接口來描述其賦予的能力。這些接口包括:IOutputChannel、IInputChannel、IRequestChannel、IReplyChannel、 IDuplexChannel,它們均定義在System.ServiceModel.Channels命名空間下。

下麵的表格簡單列出了在不同的消息交換模式下,消息的發送方和接收方所使用的信道:

image

 

5所示的類圖簡單地描述了這些接口之間的層次結構:所有的接口均繼承自IChannel接口,IDuplexChannel則繼承了IOutputChannel和IInput、Channel兩個接口。

image

5 Channel Shape

IOutChannel IInputChannel

接下來我們對這五種信道進行逐個介紹,先從IOutputChannel和IInputChannel開始。這兩種類型的信道適用於基於數據報模式的消息交換中,發送端通過IOutputChannel發送消息,而接收端則通過IInputChannel接收消息。反應在接口的定義上,IOutputChannel主要定義的Send方法進行消息的發送,而IInputChannel則定義Receive方法進行消息的接收。先來看看IOutputChannel的定義:

   1: public interface IOutputChannel : IChannel, ICommunicationObject
   2: {
   3:     // Methods
   4:     void Send(Message message);
   5:     void Send(Message message, TimeSpan timeout);
   6:  
   7:     IAsyncResult BeginSend(Message message, AsyncCallback callback, object state);
   8:     IAsyncResult BeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state);
   9:     void EndSend(IAsyncResult result);
  10:  
  11:     // Properties
  12:     EndpointAddress RemoteAddress { get; }
  13:     Uri Via { get; }
  14: }

 

 

IOutputChannel的定義顯得異常簡單,兩個重載的Send方法以同步的方式進行消息的發送,而兩個BeginSend/EndSend則用於消息的異步發送。重載方法通過一個timeout參數區分。對於一個具體的信道類型來說,它一般會繼承自ChannelBase類型。在上麵我們已經介紹了ChannelBase實現了接口System.ServiceModel.IDefaultCommunicationTimeouts接口,所以它具有默認的發送超時時限(SendTimout)。因此,在調用沒有timeout參數的Send或者BeginSend方法時,實際上采用的是自己默認的消息發送超時時限。

除了用於消息發送的方法成員之外,IOutputChannel還具有兩個額外的屬性成員:RemoteAddress和Via。RemoteAddress代表它試圖訪問的服務終結點的地址,而Via則代表是消息會真正發送的目的地址。RemoteAddress和Via所代表的地址 也就是在第二章介紹的邏輯地址和物理地址。在一般的情況下,這兩個地址是相同的,在需要進行手工尋址的情況下,它們可以是完全不同的兩個地址,關於WCF的尋址,請參閱第二章。

了解了IOutputChannel的定義,我想讀者應該可以大體上猜得到與之相對的IInputChannel的定義了。IInputChannel用於消息的接收,所以定義了一係列Receive和BeginReceive/EndReceive方法用於同步或者異步的方式接收消息。不過IInputChannel較之IOutputChannel稍微複雜一些,它還定義了兩組額外的方法成員:TryReceive和BeginTryReceive/EndTryReceive,以及WaitForMessage和BeginWaitForMessage/EndWaitForMessage。

   1: public interface IInputChannel : IChannel, ICommunicationObject
   2: {
   3:     // Methods
   4:     Message Receive();
   5:     Message Receive(TimeSpan timeout);
   6:     IAsyncResult BeginReceive(AsyncCallback callback, object state);
   7:     IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback, object state);
   8:     Message EndReceive(IAsyncResult result);
   9:  
  10:     bool TryReceive(TimeSpan timeout, out Message message);
  11:     IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback, object state);
  12:     bool EndTryReceive(IAsyncResult result, out Message message);
  13:  
  14:     bool WaitForMessage(TimeSpan timeout);
  15:     IAsyncResult BeginWaitForMessage(TimeSpan timeout, AsyncCallback callback, object state);
  16:     bool EndWaitForMessage(IAsyncResult result);
  17:  
  18:     // Properties
  19:     EndpointAddress LocalAddress { get; }
  20: }

 

調用TryReceive和BeginTryReceive/EndTryReceive方法,在一個給定的時間範圍內嚐試去接收請求消息,而WaitForMessage和BeginWaitForMessage/EndWaitForMessage則用於檢測是否有請求消息抵達。此外IOutputChannel的LocalAddress屬性代表信道所屬終結點的地址。

IRequestChannelIReplyChannel

IRequestChannel和IReplyChannel定義了在請求-回複模式下消息發送方和接收方對信道的基本要求。對於消息的發送方的信道來說,它的主要功能就是向接收方發送消息請求並接收接收方發回的回複消息;與之相對,消息接收方負責對消息請求的接收,以及對回複消息的發送。

所以IRequestChannel的主要方法成員就是一組Request和BeginRequest/EndRequest方法用於同步和異步下請求的發送。整個IRequestChannel的定義如下所示 :

public interface IRequestChannel : IChannel, ICommunicationObject
{
// Methods
Message Request(Message message);
Message Request(Message message, TimeSpan timeout);

IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state);
IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state);
Message EndRequest(IAsyncResult result);

// Properties
EndpointAddress RemoteAddress { get; }
Uri Via { get; }
}

 

和IOutputChannel接口一樣,Request和BeginRequest方法各有兩個重載,它們通過一個timeout參數進行區分。Timeout參數代表請求發送(同步或者異步)的超時時限,如果沒有此參數,則采用默認的超時時限。兩個屬性RemoteAddress和Via則分別表示目的終結點的地址,以及消息真正發送的目的地址。換句話說,RemoteAddress和Via所代表的是在第二章介紹的邏輯地址和物理地址。

IReplyChannel和IInputChannel的成員結構很相似,不過IInputChannel的主要功能就就是單純的接收消息,所以定義了一係列Receive相關的方法;而IReplyChannel負責接受請求,所以IReplyChannel圍繞著ReceiveRequest展開。包括3種類型的ReceiveRequest方法:ReceiveRequest和BeginReceiveRequest/EndReceiveRequest,TryReceiveRequest和BeginTryReceiveRequest/EndTryReceiveRequest和WaitForRequest和BeginWaitForRequest和EndWaitForRequest。

   1: public interface IReplyChannel : IChannel, ICommunicationObject
   2: {
   3:     // Methods 
   4:  
   5:     RequestContext ReceiveRequest();
   6:     RequestContext ReceiveRequest(TimeSpan timeout);
   7:     IAsyncResult BeginReceiveRequest(AsyncCallback callback, object state);
   8:     IAsyncResult BeginReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state);
   9:     RequestContext EndReceiveRequest(IAsyncResult result);
  10:  
  11:     bool TryReceiveRequest(TimeSpan timeout, out RequestContext context);
  12:     IAsyncResult BeginTryReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state);
  13:     bool EndTryReceiveRequest(IAsyncResult result, out RequestContext context);
  14:  
  15:     bool WaitForRequest(TimeSpan timeout);
  16:     IAsyncResult BeginWaitForRequest(TimeSpan timeout, AsyncCallback callback, object state);
  17:     bool EndWaitForRequest(IAsyncResult result);
  18:  
  19:     // Properties
  20:     EndpointAddress LocalAddress { get; }
  21: }

 

對於IReplyChannel來說,有一點需要特別說明。和我們一般想象的不一樣,不論是ReceiveRequest的返回類型,還是EndTryReceiveRequest的輸出參數類型,並不是一個Message類型,而是一個RequestContext類型。RequestContext可以看成是請求和回複之間的一道橋梁,通過RequestContext既可以獲取請求消息(通過RequestContext的RequestMessage屬性獲取以Message類型返回德請求消息),也可以向請求端發送回複消息(在RequestContext定義了一係列Reply和BeginReply/EndReply方法將作為參數的Message對象發回請求端)。

IDuplexChannel

由於在雙工模式下的消息交換中,消息的發送端和接收端具有相同的行為和功能:消息的發送和接收,所以基於雙工模式的信道, IDuplexChannel兼具IOutputChannel和IInputChannel的特性。反映在接口的定義上就是IDuplexChannel同時繼承了IOutputChannel和IInputChannel:

   1: public interface IDuplexChannel : IInputChannel, IOutputChannel, IChannel, ICommunicationObject
   2: {
   3: }

WCF中的綁定模型:
[WCF中的Binding模型]之一: Binding模型簡介
[WCF中的Binding模型]之二: 信道與信道棧(Channel and Channel Stack)
[WCF中的Binding模型]之三:信道監聽器(Channel Listener)
[WCF中的Binding模型]之四:信道工廠(Channel Factory)
[WCF中的Binding模型]之五:綁定元素(Binding Element)
[WCF中的Binding模型]之六:從綁定元素認識係統預定義綁定



作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 16:04:20

  上一篇:go  關於Type Initializer和 BeforeFieldInit的問題,看看大家能否給出正確的解釋
  下一篇:go  Enterprise Library深入解析與靈活應用(5):創建一個簡易版的批處理執行器,認識Enterprise Library典型的配置方式和對象創建方式