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所示 的类图所示。
图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: }
三、 消息交换模式与Channel Shape
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所示,在数据报模式下,消息的发送方将消息发送到接收方,并不希望收到对象的回复。
图2数据报消息交换模式
数据报模式具有一些变形,比较典型的包括以下一些消息交换的方式:
- 单目的地模式:一个消息的发送方将消息发送给单一的接收方
- 多投模式:一个消息发送方将消息发送给一系列预定义的接收方
- 广播模式:和多投模式相似,只是接收方的范围更加宽泛
数据报模式一般采用异步的消息发送方式,并不希望接收到对方的回复消息,在个别情况下甚至不关心消息能否正常地被接收。
请求/回复模式(Request/Reply)
在这三种典型的消息交换模式中,请求/回复模式是使用得最多的一种模式。在这种模式下,消息发送方来将消息发送给接收方后会等待对方的回复。请求/回复模式的消息交换情况如下图所示。请求/回复模式一般采用同步的通信模式(尽管该模式也可以用于异步通信)。
图3 请求-回复消息交换模式
双工模式(Duplex)
如果采用双工的消息交换模式,在进行消息交换过程中,任何一方都可以向对方发送消息,如图4所示。
图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命名空间下。
下面的表格简单列出了在不同的消息交换模式下,消息的发送方和接收方所使用的信道:
图5所示的类图简单地描述了这些接口之间的层次结构:所有的接口均继承自IChannel接口,IDuplexChannel则继承了IOutputChannel和IInput、Channel两个接口。
图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属性代表信道所属终结点的地址。
IRequestChannel和IReplyChannel
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
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。