本节目录:
数据报通道:IInputChannel与IOutputChannel
请求/应答通道:IRequestChannel和IReplyChannel
IDefaultCommunicationTimeouts接口
通道接口和基本类型
本章开始部分曾经提到过,学习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的类型可以根据需要提供自己的实现。
双工通道: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方法实现的方式类似。
【老徐的博客】
【作 者】:Frank Xu Lei
【地 址】:http://www.cnblogs.com/frank_xl/
【中文论坛】:微软WCF中文技术论坛
【英文论坛】:微软WCF英文技术论坛