3.5.2 绑定揭秘
前面一直在谈论信道、信道管理器、信道监听器、信道工厂和绑定元素,现在我们进入本章的主题——绑定。不过由于前面的铺垫已经很足了,绑定本身反而没有太多可大书特书的地方。如果从结构上给绑定下个定义,那么我的定义很简单:“绑定是绑定元素的有序集合”。
绑定是绑定元素的有序集合
由于绑定的终极目标是实现对信道栈的创建,而对于一个信道栈来说,信道的构成和次序决定着该信道栈在最终消息通信中的特性与能力。绑定元素又决定着信道的创建,所以绑定对象本身的特性与能力由自身包含的所有绑定元素及这些绑定元素之间的先后次序决定。
当我们须要判断某一个绑定类型是否支持某种特性的时候,只须查看该绑定是否具有与此特性相关的绑定元素。比如,我们要判断某种绑定是否支持某种类型的传输协议,只须看看构成该绑定的传输绑定元素就可以了,WsHttpBinding的传输绑定元素是HttpTransportBindingElement,所以它只能支持基于HTTP或HTTPS的传输协议;如果须要确定某种类型的绑定是否支持事务的流转,只须查看该绑定的绑定元素集合中是否包含TransactionFlowBindingElement就可以了。
在WCF中,所有的绑定都直接或间接继承自抽象基类:System.ServiceModel.Channels. Binding,现在来简单地分析一下这个基类。首先,Binding实现了接口IDefaultCommunication-Timeouts。4个默认的超时时限可以通过编程的方式显式指定,也可以通过配置的方式进行设置。他们的默认值分别为:OpenTimeout和CloseTimeout为1分钟,而SendTimeout和ReceiveTimeout为10分钟。
public abstract class Binding : IDefaultCommunicationTimeouts { //其他成员 public TimeSpan OpenTimeout { get; set; } public TimeSpan CloseTimeout { get; set; } public TimeSpan SendTimeout { get; set; } public TimeSpan ReceiveTimeout { get; set; } }
对于Binding,最为重要的就是如何构建组成该绑定对象的所有绑定元素集合。基于绑定元素的创建,通过抽象方法CreateBindingElements实现,所有具体的绑定类型均须要实现该方法。
public abstract class Binding : IDefaultCommunicationTimeouts { //其他成员 public abstract BindingElementCollection CreateBindingElements(); }
由于信道管理器栈创建相应的信道栈,而绑定创建信道管理器栈,因此在Binding中定义了一系列BuildChannelFactory<TChannel>和BuildChannelListener<TChannel>方法重载,用于创建信道工厂和信道监听器。此外,和BindingElement一样,CanBuildChannelFactory<TChannel>和CanBuildChannelListener<TChannel>用于检测绑定对象创建信道工厂和信道监听器的能力:
public abstract class Binding : IDefaultCommunicationTimeouts { //其他成员 public IChannelFactory<TChannel> BuildChannelFactory<TChannel>(params object[] parameters); public virtual IChannelFactory<TChannel> BuildChannelFactory<TChannel> (BindingParameterCollection parameters); public virtual IChannelListener<TChannel> BuildChannelListener<TChannel> (params object[] parameters) where TChannel : class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel> (BindingParameterCollection parameters) where TChannel : class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel> (Uri listenUriBaseAddress, params object[] parameters) where TChannel : class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel> (Uri listenUriBaseAddress, BindingParameterCollection parameters) where TChannel : class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel> (Uri listenUriBaseAddress, string listenUriRelativeAddress, params object[] parameters) where TChannel : class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel> (Uri listenUriBaseAddress, string listenUriRelativeAddress, BindingParameterCollection parameters) where TChannel : class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel> (Uri listenUriBaseAddress, string listenUriRelativeAddress, ListenUriMode listenUriMode, params object[] parameters) where TChannel : class, IChannel; public virtual IChannelListener<TChannel> BuildChannelListener<TChannel> (Uri listenUriBaseAddress, string listenUriRelativeAddress, ListenUriMode listenUriMode, BindingParameterCollection parameters) where TChannel : class, IChannel; public virtual bool CanBuildChannelFactory<TChannel> (BindingParameterCollection parameters); public bool CanBuildChannelFactory<TChannel>(params object[]parameters); public bool CanBuildChannelListener<TChannel>(params object[]parameters) where TChannel : class, IChannel; public virtual bool CanBuildChannelListener<TChannel> (BindingParameterCollection parameters) where TChannel : class, IChannel; }
案例演示:如何创建自定义绑定
在之前一系列的案例演示中,我们先后创建了自定义的信道、信道监听器、信道工厂与绑定元素。这些对象最终只有通过具体的绑定对象才能应用到WCF的运行环境中,接下来,就演示如何通过自定义绑定应用创建绑定的元素。
1.自定义数据报绑定
为了简单起见,对于我们自定义的绑定,仅仅包含3个必须的绑定元素:传输绑定元素和消息编码绑定元素,外加自定义的绑定元素。对于传输,我们采用基于HTTP协议的HttpTransportBindingElement,而对应消息编码,则采用基于文本编码方式的TextMessageEncodingBindingElement。我们将自定义的绑定命名为SimpleDatagramBinding。
namespace Artech.CustomChannels { public class SimpleDatagramBinding:Binding { private TransportBindingElement _transportBindingElement = new HttpTransportBindingElement(); private MessageEncodingBindingElement _messageEncodingBindingElement = new TextMessageEncodingBindingElement(); private SimpleDatagramBindingElement _SimpleDatagramBindingElement = new SimpleDatagramBindingElement(); public override BindingElementCollection CreateBindingElements() { BindingElementCollection elemens=new BindingElementCollection(); elemens.Add(this._SimpleDatagramBindingElement); elemens.Add(this._messageEncodingBindingElement); elemens.Add(this._transportBindingElement); return elemens.Clone(); } public override string Scheme { get { return this._transportBindingElement.Scheme; } } } }
对于整个SimpleDatagramBinding的定义,仅仅重写了CreateBindingElements方法和Scheme属性。CreateBindingElements方法返回一个表示绑定元素集合的BindingElement-Collection对象,在该集合中,包含3种类型的绑定元素,从上到下的顺序分别为:SimpleDatagramBindingElement、MessageEncodingBindingElement和HttpTransportBinding-Element。Scheme是只读属性的,它返回HttpTransportBindingElement的Scheme:http。
2. 在WCF应用中使用自定义绑定
既然自定义绑定已经被创建出来了,我们先通过一个具体的WCF应用例子来使用创建的SimpleDatagramBinding。WCF应用照例采用典型的4层结构:Contracts、Services、Hosting和Clients(如图3-15所示)。
图3-15 案例解决方案结构
先来介绍Contract和Service的定义,为了简单起见,我们就定义一个DoSomething操作,服务与服务契约的定义如下所示。
using System.ServiceModel; namespace Artech.CustomChannels.Contracts { [ServiceContract(Namespace="http://www.artech.com/")] public interface IService { [OperationContract] void DoSomething(); } }
using System; using Artech.CustomChannels.Contracts; namespace Artech.CustomChannels.Services{ public class Service:IService { public void DoSomething() { Console.WriteLine("Done..."); } } }
在服务的寄宿程序中,通过调用ServiceHost对象的AddServiceEndpoint方法将终结点的绑定指定成自定义的绑定:SimpleDatagramBinding。
using System; using System.ServiceModel; using Artech.CustomChannels.Services; using Artech.CustomChannels.Contracts; namespace Artech.CustomChannels.Hosting { class Program { static void Main(string[] args) { using (ServiceHost host = new ServiceHost(typeof(Service))) { host.AddServiceEndpoint(typeof(IService), new SimpleDatagramBinding(), "http://127.0.0.1:9999/service"); host.Open(); Console.Read(); } } } }
服务的客户端实际将SimpleDatagramBinding对象和终结点地址作为参数创建ChannelFactory<IService>对象,最后通过该对象创建的服务代理进行服务的调用。为了演示信道栈与客户端(服务代理)之间的关系,我们先后创建了两个服务代理对象,代码如下。
using System; using System.ServiceModel; using System.ServiceModel.Channels; using Artech.CustomChannels.Contracts; namespace Artech.CustomChannels.Clients { class Program { static void Main(string[] args) { EndpointAddress address = new EndpointAddress ("http://127.0.0.1:9999/service"); using (ChannelFactory<IService> channelFactory = new ChannelFactory <IService>(new SimpleDatagramBinding(), address)) { //创建第一个服务代理 IService proxy = channelFactory.CreateChannel(); proxy.DoSomething(); proxy.DoSomething(); (proxy as ICommunicationObject).Close(); //创建第二个服务代理 proxy = channelFactory.CreateChannel(); proxy.DoSomething(); proxy.DoSomething(); (proxy as ICommunicationObject).Close(); } Console.Read(); } } }
本案例的目的不仅仅在于演示如何在WCF应用中使用自定义绑定,还想通过一个具体、鲜活的例子让读者认识到在进行服务寄宿、服务调用的过程中,绑定模型中的一系列对象:信道、信道监听器、信道工厂及绑定元素之间是如何交互的。由于在自定义信道、信道监听器、信道工厂及自定义绑定元素的大部分方法中都通过PrintHelper输出了当前执行的方法名,所以可以根据输出大体地看出整个执行流程。
下面是在服务成功寄宿后(尚无任何服务调用)服务端的输出结果。大体上可以从中认识到服务寄宿过程中,服务端信道层是如何工作的。对于服务的每一个终结点(具有不同的监听地址),WCF会遍历绑定的所有绑定元素,并调用BuildChannelListener<TChannel>创建信道监听器;信道监听器开启之后,调用BeginAcceptChannnel创建信道。信道开启之后,信道监听器监听来自用户的请求,并利用创建好的信道栈接收请求消息。
SimpleDatagramBindingElement.BuildChannelListener<TChannel> SimpleDatagramChannelListener^1.SimpleDatagramChannelListener SimpleDatagramChannelListener^1.OnOpen SimpleDatagramChannelListener^1.OnBeginAcceptChannnel SimpleDatagramChannelListener^1.OnEndAcceptChannnel SimpeReplyChannel.SimpeReplyChannel SimpleDatagramChannelListener^1.OnBeginAcceptChannnel SimpeReplyChannel.OnOpen SimpeReplyChannel.BeginTryReceiveRequest
如果执行完客户端的所有服务调用程序,客户端将会具有如下的输出。从中反映出的流程是这样的:遍历当前终结点绑定对象的所有绑定元素,调用BuildChannelFactory<TChannel>创建信道工厂(此时ChannelFactory<IService>被创建);信道工厂正常开启后,通过CreateChannel方法创建信道(此时服务代理通过ChannelFactory<IService>对象创建);在通过信道进行第一次调用(或者显式调用服务代理的Open方法)时,信道被开启,被开启的信道可以被多次用于服务调用。如果显式关闭服务代理对象,基于它的客户端信道栈也将被关闭。
SimpleDatagramBindingElement.BuildChannelFactory<TChannel> SimpleDatagramChannelFactory^1.SimpleDatagramChannelFactory SimpleDatagramChannelFactory^1.OnOpen SimpleDatagramChannelFactory^1.OnCreateChannel SimpleRequestChannel.SimpleRequestChannel SimpleRequestChannel.OnOpen SimpleRequestChannel.Request SimpleRequestChannel.Request SimpleRequestChannel.OnClose SimpleDatagramChannelFactory^1.OnCreateChannel SimpleRequestChannel.SimpleRequestChannel SimpleRequestChannel.OnOpen SimpleRequestChannel.Request SimpleRequestChannel.Request SimpleRequestChannel.OnClose
当客户端正常地执行完毕,服务端会多了如下一些额外的输出。通过这些输出可以看出,虽然客户端程序先后创建了两个服务代理进行服务调用,但是服务端用于接收请求消息的信道栈仅有一个。
SimpeReplyChannel.EndTryReceiveRequest SimpeReplyChannel.BeginTryReceiveRequest Done… SimpeReplyChannel.EndTryReceiveRequest SimpeReplyChannel.BeginTryReceiveRequest Done… SimpeReplyChannel.EndTryReceiveRequest SimpeReplyChannel.BeginTryReceiveRequest Done… SimpeReplyChannel.EndTryReceiveRequest SimpeReplyChannel.BeginTryReceiveRequest Done
3.自定义会话绑定
接下来,我们创建一个自定义会话绑定,并应用之前创建的会话绑定元素SimpleSessionBindingElement。CreateBindingElements方法返回的集合中包含3种类型的绑定元素:
● SimpleSessionBindingElement:自定义会话绑定元素。
● BinaryMessageEncodingBindingElement:基于二进制编码的消息编码绑定元素。
● TcpTransportBindingElement:基于TCP的传输绑定元素。
using System.ServiceModel.Channels; namespace Artech.CustomChannels { public class SimpleSessionBinding:Binding { private SimpleSessionBindingElement _simpleElement = new SimpleSessionBindingElement(); private MessageEncodingBindingElement _encodingElement = new BinaryMessageEncodingBindingElement(); private TransportBindingElement _transportBindingElement = new TcpTransportBindingElement(); public override BindingElementCollection CreateBindingElements() { BindingElementCollection elemens=new BindingElementCollection(); elemens.Add(this._simpleElement); elemens.Add(this._encodingElement); elemens.Add(this._transportBindingElement); return elemens.Clone(); } public override string Scheme { get { return this._transportBindingElement.Scheme; } } } }
为了比较数据报绑定与会话绑定在消息交换过程不同的表现行为,我们将SimpleSessionBinding应用到上面的WCF应用中,仅仅替换到服务端和客户端绑定即可。
static void Main(string[] args) { using (ServiceHost host = new ServiceHost(typeof(Service))) { host.AddServiceEndpoint(typeof(IService), new SimpleSessionBinding(), "net.tcp://127.0.0.1:9999/service"); host.Open(); Console.Read(); } }
static void Main(string[] args) { EndpointAddress address = new EndpointAddress ("net.tcp://127.0.0.1:9999/service"); using (ChannelFactory<IService> channelFactory = new ChannelFactory<IService>(new SimpleSessionBinding(),address)) { //...... } }
在服务成功寄宿后,服务端输出如下。这和数据报绑定的表现形式有所不同:数据报绑定在开始监听的时候就已经通过信道监听器创建了信道栈,而会话绑定在监听的时候,信道栈还没有被建立起来。
SimpleSessionBindingElement.BuildChannelListener<TChannel> SimpleSessionChannelListener^1.SimpleSessionChannelListener SimpleSessionChannelListener^1.OnOpen SimpleSessionChannelListener^1.OnBeginAcceptChannel
在客户服务调用程序正常执行后,客户端会具有如下的输出。这和前面例子中的输出类似,不同的仅仅是请求信道通过Request方法向服务端发送请求和接收回复,双工通信则通过调用Send和Receive发送和接收消息。
SimpleSessionBindingElement.BuildChannelFactory<TChannel> SimpleSessionChannelFactory^1.SimpleSessionChannelFactory SimpleSessionChannelFactory^1.OnOpen SimpleSessionChannelFactory^1.OnCreateChannel SimpleDuplexSessionChannel.SimpleDuplexSessionChannel SimpleDuplexSessionChannel.OnOpen SimpleDuplexSessionChannel.Send SimpleDuplexSessionChannel.TryReceive SimpleDuplexSessionChannel.Send SimpleDuplexSessionChannel.TryReceive SimpleDuplexSessionChannel.OnClose SimpleSessionChannelFactory^1.OnCreateChannel SimpleDuplexSessionChannel.SimpleDuplexSessionChannel SimpleDuplexSessionChannel.OnOpen SimpleDuplexSessionChannel.Send SimpleDuplexSessionChannel.TryReceive SimpleDuplexSessionChannel.Send SimpleDuplexSessionChannel.TryReceive SimpleDuplexSessionChannel.OnClose
会话绑定不同于数据报信道的工作机制主要通过服务端的输出体现,当服务调用程序正常运行时,服务端多出了如下一段输出。仔细分析输出结果,你会发现对于会话信道栈的创建发生在成功监听到调用请求的时候。会话信道栈与具体的客户端绑定,当关闭客户端会话信道栈的时候,服务端的会话信道栈也会相应关闭。
SimpleSessionChannelListener^1.OnEndAcceptChannel SimpleDuplexSessionChannel.SimpleDuplexSessionChannel SimpleSessionChannelListener^1.OnBeginAccesptChannel SimpleDuplexSessionChannel.OnOpen SimpleDuplexSessionChannel.BegingTryReceive SimpleDuplexSessionChannel.EndTryReceive Done... SimpleDuplexSessionChannel.Send SimpleDuplexSessionChannel.BegingTryReceive SimpleDuplexSessionChannel.EndTryReceive Done... SimpleDuplexSessionChannel.Send SimpleDuplexSessionChannel.BegingTryReceive SimpleDuplexSessionChannel.EndTryReceive SimpleDuplexSessionChannel.OnClose SimpleSessionChannelListener^1.OnEndAcceptChannel SimpleDuplexSessionChannel.SimpleDuplexSessionChannel SimpleSessionChannelListener^1.OnBeginAccesptChannel SimpleDuplexSessionChannel.OnOpen SimpleDuplexSessionChannel.BegingTryReceive SimpleDuplexSessionChannel.EndTryReceive Done... SimpleDuplexSessionChannel.Send SimpleDuplexSessionChannel.BegingTryReceive SimpleDuplexSessionChannel.EndTryReceive Done... SimpleDuplexSessionChannel.Send SimpleDuplexSessionChannel.BegingTryReceive SimpleDuplexSessionChannel.EndTryReceive SimpleDuplexSessionChannel.OnClose