通道接口和基本類型
本章開始部分曾經提到過,學習WCF通道基礎結構的一個關鍵部分就是了解 WCF系統在通道層使用的接口和類型。本節系統整理了這些復雜的類型系統,深 入淺出地講述各個接口和類型的原理,使得讀者可以更容易掌握這些知識點。
IChannel接口
System.ServiceModel.Channels.IChannel接口看似簡單,但是它對於通道層 的實現至關重要。所有的通道和通道工廠必須實現它。換句話說,一個集成了 CommunicationObject的類型通常也會實現IChannel接口。在詳細學習IChannel 接口的作用以前,我們先來看看它的基本結構:
public interface IChannel : ICommunicationObject {
T GetProperty<T>() where T: class;
}
你或許會問自己:“為什麼會這麼重要呢?”記得CommunicationObject堆棧 裡的每個CommunicationObject 對象都有一些特定的功能,並且只有棧頂的通道 才可以被調用者調用。當堆棧組合正常的情況啊,GetProperty<T>方法提 供了在 CommunicationObject堆棧裡查詢特定功能的途徑。例如,你也許想知道 CommunicationObject堆棧是否支持特定的通道外形,MessageVersion或安全功 能。下面代碼演示了調用如何使用IChannel.GetProperty<T>方法:
// assume channel stack (myChannelStack) created假定通道 堆棧已經創建完畢
MessageVersion messageVersion =
myChannelStack.GetProperty<MessageVersion>();
if(MessageVersion != null){
// do something
}
// app continues
和CommunicationObject堆棧裡的其它成員一樣,當一個通道不知道如何響應 查詢的時候,它會使用委托去調用堆棧裡的下一個通道。GetProperty<T> 的簡單實現如下:
public override T GetProperty<T>() where T: class {
if (typeof(T) == typeof(MessageVersion)) {
// this type knows only how to return MessageVersion
return (T) this.MessageVersion;
}
// no other capabalities are known here, so
// delegate the query to the next node
return this.inner.GetProperty<T>();
}
如上所示,這個GetProperty<T>方法的實現可以只返回 MessageVersion,並且它的可以調用查詢堆棧裡的下一個通道的功能。如果查詢 的功能不存在,就會返回null,而不是拋出異常。因為使用了委托來嵌套查詢, 所以只有最底層的通道查詢方法才會拋出null。
數據報通道:IInputChannel與IOutputChannel
第三張裡曾經提到,數據報消息交換模式非常強大而且極具可伸縮性。在數 據報消息交換模式裡,發送者發送一個消息到接收者,而不期望得到回復。更簡 單地說,發送者輸出(發送)一個消息,接收者接受一個消息作為輸入。因此, WCF基礎結構定義了數據報交換模式裡的發送者接口名為 System.ServiceModel.Channels.IOutputChannel,而接受者的接口名為 System.ServiceModel.IInputChannel。
發送接口:IOutputChannel
Like its role in the Datagram MEP, the IOutputChannel interface is simple, as shown here:
和其在數據報交換模式裡角色一樣,IOutputChannel接口比較簡單,如下所 示:
public interface IOutputChannel : IChannel, ICommunicationObject {
IAsyncResult BeginSend(Message message, AsyncCallback callback,
Object state);
IAsyncResult BeginSend(Message message, TimeSpan timeout,
AsyncCallback callback, Object state);
void EndSend(IAsyncResult result);
void Send(Message message);
void Send(Message message, TimeSpan timeout);
EndpointAddress RemoteAddress { get; }
Uri Via { get; }
}
首先,IOutputChannel實現了IChannel和ICommunicationObject接口。任何 實現了IOutputChannel接口的類型,都必須定義公有的通道狀態機成員和 GetProperty<T>方法。為了支持異步編程模型(APM),接口定義了同步 和異步的Send方法。
RemoteAddress屬性指的是消息發送的地址。值得注意的是,這不一定是消息 發送的真實地址。回憶一下第2章“面向服務”裡的郵政服務的例子,這在一個 消息接收者的情況下,對於標記地址十分有用。IOutputChannel上Via屬性表示 的另外一個地址是消息發送的目標地址。
接收接口:IInputChannel
接收數據報消息的通道實現了IInputChannel接口。對於接收者在數據報交換 模式裡的角色,IInputChannel只定義了接收成員而沒有發送成員。 IInputChannel接口的定義如下:
public interface IInputChannel : IChannel, ICommunicationObject {
EndpointAddress LocalAddress { get; }
// Receive Methods
IAsyncResult BeginReceive(AsyncCallback callback, Object state);
IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback,
Object state);
Message EndReceive(IAsyncResult result);
Message Receive();
Message Receive(TimeSpan timeout);
// TryReceive Methods
IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback,
Object state);
bool EndTryReceive(IAsyncResult result, out Message message);
bool TryReceive(TimeSpan timeout, out Message message);
// Waiting Methods
IAsyncResult BeginWaitForMessage(TimeSpan timeout,
AsyncCallback callback,
Object state);
bool EndWaitForMessage(IAsyncResult result);
bool WaitForMessage(TimeSpan timeout);
}
通常,接收程序會消極地等待消息到來。為此,IInputChannel定義了三個等 待消息的方法。這些方法的命名沒有什麼規律,但是為了簡便,這裡就分為 Receive、TryReceive和WaitForMessage幾組方法。所有的方法都包含同步和異 步定義。
Receive方法等待一段時間,如果消息在這段時間內到達,該方法會返回一個 Message 引用。如果在規定的時間內,消息還沒到達,這些方法就會拋出一個 TimeoutException。TryReceive方法會等待一段時間,然後通過 out參數返回一 個Message引用。這些方法返回一個Boolean值表示能否在期望的時間內返回 Message。Receive和TryReceive方法最大的不同就是如何顯示超時結果。
與Receive和TryReceive 不同,WaitForMessage方法不會返回一個Message引 用,或者一個out參數。它會返回一個表示一個消息是否到達的Boolean值。這有 點像I/O基礎結構裡的Peek功能。把WaitForMessage與Receive或TryReceive一起 使用,可以實現等待一個消息並接受一個消息。
當消息要參與到一些其他的活動中的時候,WaitForMessage方法就非常有用 。比如,思考以下情況,當一個Message必須參與到一個事務裡。在這個例子裡 ,對於Receive和TryReceive方法的調用必須包裝到事物裡。如果Message 沒有 到達,調用者必須終止事務。如果,調用者使用了WaitForMessage方法,這次調 用就沒必要發生在事務的范圍內。如果 WaitForMessage返回false,調用者僅僅 需要再調用WaitForMessage方法。一旦Message到達,調用者能夠啟動一個事務 ,然後調用Receive或TryReceive方法執行相應的任務。
請求/應答通道:IRequestChannel和IReplyChannel
在請求/應答消息交換模式裡,消息的參與者都要發送和接收消息。發送者發 送消息給接收者,然後等待回復。而接收者會接收請求消息,然後發送一個回復 消息。為了實現通道形狀,IRequestChannel和 IReplyChannel接口分別定義了 符合請求/應答消息交換模式的成員.
發送接口:IRequestChannel
IRequestChannel接口定義了發送請求消息和接收應答消息的相關成員。通道 層裡發送和接收消息的成員都包含同步和異步的定義。如下所示:
public interface IRequestChannel : IChannel, ICommunicationObject {
// Request Methods
IAsyncResult BeginRequest(Message message, AsyncCallback callback,
Object state);
IAsyncResult BeginRequest(Message message, TimeSpan timeout,
AsyncCallback callback, Object state);
Message EndRequest(IAsyncResult result);
Message Request(Message message);
Message Request(Message message, TimeSpan timeout);
EndpointAddress RemoteAddress { get; }
Uri Via { get; }
}
上面的代碼裡,Request方法接受一個Message類型的參數,然後返回一個 Message類型的實例。 這些成員方法的簽名保證了它們符合請求/應答消息交換 模式。
接收接口:IReplyChannel
支持請求/應答消息交換模式的消息接收程序必須實現IReplyChannel接口, IReplyChannel的定義如下:
public interface IReplyChannel : IChannel, ICommunicationObject {
RequestContext ReceiveRequest();
RequestContext ReceiveRequest(TimeSpan timeout);
IAsyncResult BeginReceiveRequest(AsyncCallback callback, Object state);
IAsyncResult BeginReceiveRequest(TimeSpan timeout,
AsyncCallback callback, Object state);
RequestContext EndReceiveRequest(IAsyncResult result);
Boolean TryReceiveRequest(TimeSpan timeout, out RequestContext context);
IAsyncResult BeginTryReceiveRequest(TimeSpan timeout,
AsyncCallback callback,
Object state);
Boolean EndTryReceiveRequest(IAsyncResult result,
out RequestContext context);
Boolean WaitForRequest(TimeSpan timeout);
IAsyncResult BeginWaitForRequest(TimeSpan timeout,
AsyncCallback callback,
Object state);
bool EndWaitForRequest(IAsyncResult result);
EndpointAddress LocalAddress { get; }
}
IReplyChannel裡沒有直接返回一個Message實例的成員。相反, IReplyChannel接口支持通過RequestContext類型訪問接收到的Message實例。下 一節會詳細討論 RequestContext類型。現在,我們該知道接收到的消息對於 RequestContext類型是可見的,並且可以通過 RequestContext訪問消息實例。
像IInputChannel一樣,IReplyChannel 也定義了幾類接收消息的方法。 ReceiveRequest方法返回一個RequestContext實例,並且超時的時候,會拋出異 常。 TryReceiveRequest會返回一個Boolean類型的值來表示是否在規定的時間 內接收到消息。WaitForRequest方法,和 IInputChannel接口上的 WaitForMessage方法類似,返回的結果取決於請求消息或是否超時。
請求/應答關聯:RequestContext類型
在請求/應答消息交換模式裡,請求和應答是緊密關聯的。從發送者的角度來 看,請求通常會返回一個應答消息。從接受者的角度來看,一個接收到的消息必 須產生一個應答消息。如前所述,IReplyChannel使用RequestContext作為 ReceiveRequest方法的返回類型。這是請求/應答消息交換模式下,接收通道關 聯消息的首要方式。
更高層次上,RequestContext類型包裝了請求消息,而且提供了發送應答消 息給發送者的方法。在RequestContext裡,可以通過RequestMessage屬性查看請 求消息。RequestContext的Reply方法提供了發送應答消息的途徑。和其它的通 道成員一樣,reply方法對於同步和異步方法都是可見的。下面代碼展示了 RequestContext類型的成員:
public abstract class RequestContext : IDisposable {
protected RequestContext();
public abstract void Abort();
public abstract void Reply(Message message);
public abstract void Reply(Message message, TimeSpan timeout);
public abstract IAsyncResult BeginReply(Message message,
AsyncCallback callback,
Object state);
public abstract IAsyncResult BeginReply(Message message,
TimeSpan timeout,
AsyncCallback callback,
Object state);
public abstract void EndReply(IAsyncResult result);
public abstract void Close();
public abstract void Close(TimeSpan timeout);
protected virtual void Dispose(Boolean disposing);
void IDisposable.Dispose();
public abstract Message RequestMessage { get; }
}
如代碼所示, RequestContext實現了IDisposable 接口。因為通道層裡其它 成員沒有實現IDisposable接口,所以這裡就很難看出為什麼RequestContext類 型會這麼干。 RequestContext類型實現IDisposable接口是因為RequestContext 包含了一個Message實例。第4章裡 “WCF101”裡曾經討論過,Message實例包含 一個Stream,因此必須實現IDisposable接口。因為這種關系,RequestContext 類型上的Dispose方法需要調用Message實例上的Dispose方法,這樣才能銷毀 Message 實例擁有的Stream。記住RequestContext是一個抽象類型,因此繼承 RequestContext的類型可以根據需要提供自己的實現。
注釋:與Message類型一樣,RequestContext類型明確地實現了IDisposable 接口
雙工通道:IDuplexChannel
雙工通道支持雙工消息交換模式(MEP)。與數據報和請求/應答消息交換模 式不同的是,雙工消息交換模式運行發送者和接收者自由地發送和接受消息。我 們在第3章裡曾經看到,雙工消息交換模式裡的消息通信很像電話通話。在開始 通信以前,發送者和接收者必須建立通信上下文環境。在,雙工消息交換模式裡 ,發送和接受通道形狀是相同的,因此,發送者和接收者實現了相同的接口(假 設連個消息參與者都是WCF程序)。因為雙工消息交換模式的與生俱來的自由特 性,以及接收者和發送公用相同的接口,因此只能通過發起通信來區分哪個是消 息的發送者(就像只能通過誰先撥號,來確定誰是打電話的人一樣)。
發送和接收接口:IDuplexChannel
IDuplexChannel接口實際上是IInputChannel和IOutputChannel的結合體。如 前文所述,IInputChannel 是為了實現了數據報消息接收者,而IOutputChannel 是為了實現數據報發送者。因為支持雙工通信的通道必須能夠發送和接受消息, 所以邏輯上,IDuplexChannel成員是數據報交換模式裡使用的所有成員合並的結 果。IDuplexChannel接口的定義如下:
public interface IDuplexChannel : IInputChannel, IOutputChannel, IChannel,
ICommunicationObject
{
}
IDefaultCommunicationTimeouts接口
因為大部分應用程序開發人員都不回直接接觸通道,因此通道層必須有一種 表示特定操作超時的方法。考慮到通道超時問題的時候,有4個時間敏感的操作 :打開通道、發送消息、接受消息和關閉通道。和通道層的其它功能一樣,WCF 類型系統包含一個描述超時的接口。System.ServiceModel. IDefaultCommunicationTimeouts,定義如下:
public interface IDefaultCommunicationTimeouts {
TimeSpan CloseTimeout { get; }
TimeSpan OpenTimeout { get; }
TimeSpan ReceiveTimeout { get; }
TimeSpan SendTimeout { get; }
}
IDefaultCommunicationTimeouts接口裡每個成員的作用可以從你名字裡推測 出來。綁定、通道工廠和通道都實現了這個接口。因為綁定、通道工廠和通道實 現了相同的接口,這些類型都可以傳遞超時到構造鏈中。例如,一個用戶可以在 Binding裡指定發送超時屬性(Binding提供了setter器)。如果Binding是消息 發送者的一部分,它就可以把超時屬性的值通過通道工廠的構造函數傳遞給通道 工廠。同樣,通道工廠也可以把超時屬性的值傳遞給通道的構造函數。作用上看 ,這一系列的傳遞提供給用戶可以通過API指定超時屬性的能力,並且這些設置 可以作用於通道層上。
ChannelBase類型
所有的自定義通道必須實現公共的狀態機,並且暴露GetProperty<T> 查詢機制,實現一個或者多個通道形狀,從通道工廠裡接受一個超時設置。 System.ServiceModel.Channels.ChannelBase抽象類型就是這個目的,它確保了 所有的通道成員的兼容性。下面代碼展示了ChannelBase的類型定義:
public abstract class ChannelBase : CommunicationObject,
IChannel,
ICommunicationObject,
IDefaultCommunicationTimeouts {
// Constructor with channel factory parameter
protected ChannelBase(ChannelManagerBase channelManager);
// IChannel implementation
public virtual T GetProperty<T>() where T: class;
// CommunicationObject members
protected override TimeSpan DefaultCloseTimeout { get; }
protected override TimeSpan DefaultOpenTimeout { get; }
protected override void OnClosed();
protected TimeSpan DefaultReceiveTimeout { get; }
protected TimeSpan DefaultSendTimeout { get; }
// IDefaultCommunicationTimeouts implementation
TimeSpan IDefaultCommunicationTimeouts.CloseTimeout { get; }
TimeSpan IDefaultCommunicationTimeouts.OpenTimeout { get; }
TimeSpan IDefaultCommunicationTimeouts.ReceiveTimeout { get; }
TimeSpan IDefaultCommunicationTimeouts.SendTimeout { get; }
// reference to channel factory
protected ChannelManagerBase Manager { get; }
private ChannelManagerBase channelManager;
}
ChannelManagerBase的成員表示的是工廠創建通道的方法。第7 章“通道管 理器”裡會詳細介紹這些內容。形狀,假設ChannelManagerBase類型一直會從通 道工廠裡獲取超時設置的值。注意 ChannelBase裡的TimeSpan類型的成員。以 Default開頭的成員都會從通道工廠裡獲取超時設置的值,而且這裡明確實現了 IDefaultCommunicationTimeouts的成員。如下所示:
protected override TimeSpan DefaultOpenTimeout {
get {
return ((IDefaultCommunicationTimeouts) this.channelManager).OpenTimeout;
}
}
// delegate to DefaultOpenTimeout property TimeSpan
IDefaultCommunicationTimeouts.OpenTimeout {
get {
return this.DefaultOpenTimeout;
}
}
上面的代碼僅僅介紹了通道裡open如何實現超時值的傳遞。close、send和 receive方法實現的方式類似。