3.3.3 消息交换模式与信道形状(Channel Shape)
WCF完全采用基于消息的通信方式,对服务的消费最终通过一系列的消息交换实现。WCF应用在不同的场景中按照不同的模式进行消息交换。
消息交换模式(MEP)
消息交换模式(Message Exchange Pattern:MEP)在SOA中是一个重要的概念。在W3C的文献中MEP的官方定义是这样的:MEP定义了参与者进行消息交换的模板(原文是:a template that describes the message exchange between messaging participants),这是一个很抽象的定义。实际上可以这样来理解MEP:消息交换模式(MEP)代表一系列的模板,它们定义了消息的发送者和接收者相互进行消息传输的次序。比较典型的消息交换模式包含以下3种:数据报模式(Datagram)、请求/回复模式(Request/Reply)及双工模式(Duplex)。
1.数据报模式(Datagram)
数据报模式是最简单的消息交换模式,又称为发送/遗忘(Send/Forget)或单向模式(One-way)。数据报模式基于从一个源到一个或多个目的地的单向消息传输。如图3-9所示,
图3-9 数据报消息交换模式
在数据报模式下,消息的发送方将消息发送到接收方,并不希望收到对方的回复。
数据报模式具有一些变型,比较典型的包括以下一些消息交换方式:
● 单目的地模式:一个消息的发送方将消息发送给单一的接收方。
● 多投模式:一个消息发送方将消息发送给一系列预定义的接收方。
● 广播模式:和多投模式相似,只是接收方的范围更加宽泛。
数据报模式一般采用异步的消息发送方式,并不希望接收到对方的回复消息,在个别情况下甚至不关心消息能否正常地被接收。
2.请求/回复模式(Request/Reply)
请求/回复模式是使用得最多的一种模式。在这种模式下,消息发送方将消息发送给接收方后会等待对方的回复。请求/回复模式的消息交换情况如图3-10所示。请求/回复模式一般采用同步的通信模式(尽管该模式也可以用于异步通信)。
图3-10 请求-回复消息交换模式
3. 双工模式(Duplex)
如果采用双工的消息交换模式,在进行消息交换过程中,任何一方都可以向对方发送消息,如图3-11所示。
图3-11 双工消息交换模式
双工通信使服务端回调客户端操作成为可能。比较典型双工通信是我们熟悉的订阅/发布模式。订阅/发布模式下的消息交换双方的角色从传统的发送方和接收方变成了订阅方和发布方。订阅方向发布方发送对某一主题的订阅请求,发布方接收到订阅消息后将订阅方添加到订阅列表之中。主题发布的时候,发布方提取当前主题的所有订阅方,对它们进行消息广播。
消息的交换依赖于网络传递,不同的网络传输协议对双工通信具有不同的支持方式。对于TCP来说,其协议本身就是全双工的网络通信协议,所以能够提供双工通信原生的支持。但是对于HTTP来说,它本身就是简单的基于请求/回复的网络协议,是不支持双工通信的。WCF通过WsDualHttpBinding实现了基于HTTP的双工通信,实际上是采用两个HTTP通道实现的。
信道形状(Channel Shape)
上面讨论了3种典型的消息交换模式(MEP),现在我们结合MEP再来讨论本节的主题:信道与信道栈。信道是消息交换的管道,在不同的消息交换模式下,管道在消息的发送端和接收端所起的作用是不同的。在数据报模式下,发送端的信道栈作用是输出(Output)数据报,接收端则是输入(Input)数据报;对于请求-回复模式来说,发送端的作用是发送消息请求(Request),而接收端则是恢复(Reply)请求;而在双工通信模式下,消息交换双方的地位完全是等价的,它们都具有输出(Output)和输入(Input)的功能。
WCF通过一个特殊的术语来表述不同的消息交换模式对消息交换双方信道的不同要求:信道形状(Channel Shape)。信道形状按照消息交换模式的不同,将信道进行了分类。WCF为这些信道定义了一系列的接口,并基于不同形状的信道进行了抽象。这些接口包括:IOutputChannel、IInputChannel、IRequestChannel、IReplyChannel、IDuplexChannel,它们均定义在System.ServiceModel.Channels命名空间下。
表3-1简单列出了在不同的消息交换模式下,消息的发送方和接收方所使用的信道。
表3-1
图3-12所示的类图简单地描述了这些接口之间的层次结构:所有的接口均继承自IChannel接口,IDuplexChannel则继承了IOutputChannel和IInputChannel两个接口。
图3-12 Channel Shape
1.IOutChannel与IInputChannel
IOutputChannel和IInputChannel这两种类型的信道适用于基于数据报模式的消息交换,发送端通过IOutputChannel发送消息,而接收端则通过IInputChannel接收消息。反映在接口定义上,IOutputChannel主要定义Send方法进行消息的发送,而IInputChannel则定义Receive方法进行消息的接收。下面是IOutputChannel的定义:
public interface IOutputChannel : IChannel, ICommunicationObject { void Send(Message message); void Send(Message message, TimeSpan timeout); IAsyncResult BeginSend(Message message, AsyncCallback callback, object state); IAsyncResult BeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state); void EndSend(IAsyncResult result); EndpointAddress RemoteAddress { get; } Uri Via { get; } }
IOutputChannel定义了两个重载的Send方法以同步的方式进行消息的发送,BeginSend/EndSend则用于消息的异步发送。对于一个具体的信道类型来说,它一般会继承自ChannelBase类型。上面已经介绍了ChannelBase实现了IDefaultCommunicationTimeouts接口,所以它具有默认的发送超时时限(SendTimeout)。因此,在调用没有timeout参数的Send或BeginSend方法时,实际上采用的是自己默认的消息发送超时时限。
除了用于消息发送的方法成员之外,IOutputChannel还具有两个额外的属性成员:RemoteAddress和Via。RemoteAddress代表它试图访问的服务终结点地址,而Via则代表消息真正发送的目的地址。RemoteAddress和Via所代表的地址,就是前面一章介绍的客户端的逻辑地址和物理地址。
与IOutputChannel相对应,IInputChannel用于消息的接收,所以定义了一系列Receive和BeginReceive/EndReceive方法用于以同步或异步的方式接收消息。下面是IInputChannel的定义。
public interface IInputChannel : IChannel, ICommunicationObject { // Methods Message Receive(); Message Receive(TimeSpan timeout); IAsyncResult BeginReceive(AsyncCallback callback, object state); IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback, object state); Message EndReceive(IAsyncResult result); bool TryReceive(TimeSpan timeout, out Message message); IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback, object state); bool EndTryReceive(IAsyncResult result, out Message message); bool WaitForMessage(TimeSpan timeout); IAsyncResult BeginWaitForMessage(TimeSpan timeout, AsyncCallback callback, object state); bool EndWaitForMessage(IAsyncResult result); // Properties EndpointAddress LocalAddress { get; } }
IInputChannel还定义了两组额外的方法成员:TryReceive和BeginTryReceive/EndTryReceive,以及WaitForMessage和BeginWaitForMessage/EndWaitForMessage。TryReceive和BeginTryReceive/EndTryReceive方法用于在一个给定的时间范围内尝试去接收请求消息,而WaitForMessage和BeginWaitForMessage/EndWaitForMessage则用于检测是否有请求消息抵达。此外IOutputChannel的LocalAddress属性代表信道所属终结点的地址。
2.IRequestChannel和IReplyChannel
IRequestChannel和IReplyChannel抽象了在请求-回复模式下消息发送方和接收方对信道的基本行为。消息发送方的信道主要功能就是向接收方发送消息请求,并从接收方接收发回的回复消息;消息接收方信道则负责对消息请求的接收,以及对回复消息的发送。所以IRequestChannel的主要方法成员就是一组Request和BeginRequest/EndRequest方法,用于同步和异步下请求的发送。
public interface IRequestChannel : IChannel, ICommunicationObject { 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); EndpointAddress RemoteAddress { get; } Uri Via { get; } }
和IOutputChannel接口一样,Request和BeginRequest方法各有两个基于timeout参数的重载。Timeout参数代表请求发送(同步或异步)的超时时限,如果没有此参数,则采用默认的超时时限。RemoteAddress和Via具有与IOutputChannel相同的含义。
IReplyChannel和IInputChannel的成员结构很相似,不过IInputChannel的主要功能就是单纯的接收消息,所以定义了一系列Receive相关的方法;而IReplyChannel负责接受请求,所以IReplyChannel围绕着ReceiveRequest展开。包括3种类型的ReceiveRequest方法:ReceiveRequest和BeginReceiveRequest/EndReceiveRequest;TryReceiveRequest和BeginTryReceiveRequest/EndTryReceiveRequest;WaitForRequest,BeginWaitForRequest/End-WaitForRequest。
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); bool TryReceiveRequest(TimeSpan timeout, out RequestContext context); IAsyncResult BeginTryReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state); bool EndTryReceiveRequest(IAsyncResult result, out RequestContext context); bool WaitForRequest(TimeSpan timeout); IAsyncResult BeginWaitForRequest(TimeSpan timeout, AsyncCallback callback, object state); bool EndWaitForRequest(IAsyncResult result); EndpointAddress LocalAddress { get; } }
对于IReplyChannel来说,有一点需要特别说明。和我们想象的不一样,不论是ReceiveRequest的返回类型,还是EndTryReceiveRequest的输出参数类型,它们都不是一个Message类型,而是一个System.ServiceModel.Channels.RequestContext类型。RequestContext可以看成是请求和回复之间的一道桥梁,通过RequestContext既可以获取请求消息(通过RequestContext的RequestMessage属性获取以Message类型返回的请求消息),也可以向请求端发送回复消息(在RequestContext定义了一系列Reply和BeginReply/EndReply方法,将作为参数的Message对象发回请求端)。
3.IDuplexChannel
由于在双工模式下的消息交换中,消息的发送端和接收端具有相同的行为:消息的输出与输入,因此双工信道具备了输出和输入信道两种特性。反映在API的定义上面,IDuplexChannel同时继承了IOutputChannel和IInputChannel:
public interface IDuplexChannel : IInputChannel, IOutputChannel, IChannel, ICommunicationObject { }
会话信道(Session Channel)
从状态保持的角度,可以把信道分为两种类型:数据报信道(Datagram Channel)和会话信道(Sessionful Channel),前者不须要保持具体的客户端(服务代理),它并不和某个具体的客户端对象绑定,因此多个客户端对象可以使用相同的信道。后者则具有客户端识别能力,一个信道对应着一个客户端对象。WCF中的会话(Session)表示进行通信的参与者之间共享上下文。对于基于消息交换的通信来说,WCF的会话通过消息关联(Message Correlation)的机制实现。关于WCF的会话,在第9章中会有详细的介绍。
1.ISession
WCF通过接口System.ServiceModel.Channels.ISession表示会话,ISession中具有一个唯一的属性成员Id,它表示会话的唯一标识。
public interface ISession { string Id { get; } }
WCF定义了一系列额外接口来表示基于不同的消息交换模式和信道形状下的会话,这些接口直接或间接地继承自ISession接口。这些接口包括:IOutputSession、IInputSession和IDuplexSession。
public interface IInputSession : ISession { } public interface IOutputSession : ISession { } public interface IDuplexSession : IInputSession, IOutputSession, ISession { IAsyncResult BeginCloseOutputSession(AsyncCallback callback, object state); IAsyncResult BeginCloseOutputSession(TimeSpan timeout, AsyncCallback callback, object state); void CloseOutputSession(); void CloseOutputSession(TimeSpan timeout); void EndCloseOutputSession(IAsyncResult result); }
在IOutputSession和IInputSession下,并没有定义任何成员。IDuplexSession继承了3个接口IInputSession、IOutputSession、ISession,定义了一系列CloseOutputSession和BeginCloseOutputSession/EndCloseOutputSession方法,用于在客户端关闭会话。
2.ISessionChannel<TSession>
会话依赖于会话信道(栈)的建立,在WCF中,所有会话信道直接或间接地实现了接口System.ServiceModel.Channels.ISessionChannel<TSession>。泛型类型TSession实现了接口ISession,下面是ISessionChannel<TSession>的定义。
public interface ISessionChannel<TSession> where TSession : ISession { TSession Session { get; } }
WCF定义了一系列基于不同信道形状下的会话信道的接口,这些接口包括:IOutputSessionChannel、IInputSessionChannel、IRequestSessionChannel、IReplySessionChannel和IDuplexSessionChannel。它们直接或间接地继承了ISessionChannel<TSession>。
public interface IOutputSessionChannel : IOutputChannel, IChannel, ICommunicationObject, ISessionChannel<IOutputSession> { } public interface IInputSessionChannel : IInputChannel, IChannel, ICommunicationObject, ISessionChannel<IInputSession> { } public interface IRequestSessionChannel : IRequestChannel, IChannel, ICommunicationObject, ISessionChannel<IOutputSession> { } public interface IOutputSessionChannel : IOutputChannel, IChannel, ICommunicationObject, ISessionChannel<IOutputSession> { } public interface IDuplexSessionChannel : IDuplexChannel, IInputChannel, IOutputChannel, IChannel, ICommunicationObject, ISessionChannel<IDuplexSession> { }