管道是所有消息进出WCF应用程序的渠道。它的职责是以统一的方式编制和提供消息。管道中定义了传输、协议和消息拦截。管道以层级结构的形式汇总,就创建了一个管道栈。管道栈以分层的方式进行通信并处理消息。例如,一个管道栈可以使用一个TCP协议管道和一个交互协议管道共同组建。这样的一个管道栈就可以允许从客户端岛服务器端,通过网络发送和接收使用TCP协议和交互式协议的消息。
管道栈的目的是将一个消息转化为发送者和接收者之间都兼容的格式来传递消息。有两种类型的管道可以使用:传输管道和协议管道。传输管道始终防止在管道栈的底部,它的责任是使用一个传输协议传递消息。WCF提供了许多传输协议,包括HTTP、TCP、MSMQ、Peer-to-peer以及命名管道。协议管道驻留在传输管道或其他协议管道的顶端。由于协议管道驻留在其他管道的顶端,他们通常称为分层管道。协议管道的职能是通过翻译和修改消息来实现线路级别上的协议。WCF提供许多类型的协议管道。例如包括实现安全支持的、传输支持的以及可靠性支持的协议管道。
提示:传输协议
WCF提供多个传输管道,包括HTTP、TCP、MSMQ、PtoP以及命名管道。其他的传输方式在示例代码中或通过第三方插件也可以使用,这样就实现更广泛的传输,包括SMTP、FTP、UDP、WebSphere MQ,以及SQL Server 代理。这些传输管道大多可以在http://wcf.netfx3.com的网站上找到。UDP传输管道可以在Windows SDK中找到。针对WebSphere MQ可以在IBM的alphaWorks网站上找到。
通信发生时,客户端和服务端需要实例化一个互相匹配的管道栈。在.NET应用程序之间,在客户端和服务端使用同一管道栈是典型的做法。一般来说,他们的功能必须匹配。我们使用绑定来简化管道栈的创建。一个绑定捕获管道栈的配置,并知道如何在运行时创建一个管道栈。绑定构建了一个绑定元素的集合,他们通常代表管道栈中的管道。绑定和绑定元素将会在后面进行讨论。
WCF管道架构允许通过应用程序抽象的沟通,从而提供了极大的灵活性。这就使开发者构建可以暴露给多个通信机制的服务,这就使应用程序服务可以根据需求变化的时间来改变服务。例如,一个WCF服务暴露在两个.NET应用程序之间,而这个服务可以轻易的暴露给Java应用程序而不需要更改应用程序本身。同时它还支持如互操作性、持久化消息,而且传输可以根据需求的变化轻松的附加到WCF服务上。之前的微软的技术(如ASP.NET Web服务、.NET Remoting、企业服务或MSMQ)要求你为每个新的通信形式重写应用程序的操作。而使用WCF,你现在可以选择你想要的通信技术,而不需要大量的重写应用程序。
WCF的功能还针对如何使用层级结构组建一个管道栈而提供非常大的灵活性。如下图所示,一个消息如何从一个WCF客户端应用程序管道栈传递到一个给定的服务端。该服务的管道栈监听消息,然后将这些消息指派到服务端应用。
一个管道栈是一系列使用绑定元素已配置好的管道。一个预定义的管道栈也叫做一个绑定。一个绑定由一系列的绑定元素构成,就像一个管道栈由一系列的管道组成一样。在这个栈的顶端是一个协议管道。协议管道与一个消息相互作用,而且更有利于安全、可靠的消息、传输和日志等功能。一个管道栈中可能有多个协议管道,他们依赖于不同的功能需求。
传输管道负责通过一个转换协议,如TCP或HTTP,发送字节信息。他们也负责使用一个编码形式来将一个消息转化为字节数组以便于传输。这种编码形式的职能是把消息从它本身的XML表现形式转化为字节数组的形式。编码器使用绑定元素暴露给传输管道。传输通道通过MessageEncoder类查看绑定内容。如果没有匹配项,传输管到会定义一个默认的消息编码器。
提示:管道栈有一个传输器和一个编码器
管道栈有至少一个传输器和一个编码器。通常传输器将定义一个默认的编码器来使用。例如tcpTransport传输管道指定使用binaryMessageEncoding。这些都是WCF中实现一个管道栈所必须的。协议管道在编辑管道栈时是可选的。
管道类型
WCF支持三种不同的消息交换模式:单项、全双工和请求。为了方便每种模式,WCF提供了10种不同的接口,叫做管到类型。其中的5个是IOutputChannel、IInputChannel、IDuplexChannel、IRequestChannel以及IReplyChannel。这些类型对于支持会话状态来说都是对等的。他们包括IOutputSessionChannel、IInputSessionChannel、IDuplexSessionChannel、IRequestSessionChannel以及IReplySessionChannel。这些接口在管道栈中实现不同的消息交换模式。在这里我们会看到每种通信模式和各种接口的关系。
单向通信模式
单向通信模式中,消息只在一个方向上发送,就是从客户端到服务端。当发送者并不需要一个信息马上回应时,通常应用单向通信;此时发送者只需要一个消息已被发送的确认。消息发送后,通信就结束。用于实现单向通信的两个接口是IOutputChannel以及IInputChannel接口。下图显示了,单向通信中,消息是如何在客户端和服务端流动的。在这个模式中,IOutputChannel接口负责发送消息,而IInputChannel负责接收消息。下面代码显示了一个客户端程序使用IOutputChannel管道来发送消息。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels;namespace WCFOneWayChannelClient {class Program{static void Main(string[] args){BasicHttpBinding binding = new BasicHttpBinding();BindingParameterCollection parameters = new BindingParameterCollection();Message m = Message.CreateMessage(MessageVersion.Soap11,"urn:sendmessage");IChannelFactory<IOutputChannel> factory = binding.BuildChannelFactory<IOutputChannel>(parameters);IOutputChannel channel = factory.CreateChannel(new EndpointAddress("http://localhost/sendmessage"));channel.Send(m);channel.Close();factory.Close();}} }
全双工通信
双工通信使用两个单向管道,组合成第三个接口叫做IDuplexChannel,如下图所示。双工通信与单向或请求响应模式相比,优点在于消息可以在客户端和服务端之间互相发送。
全双工通信的一个例子是一个事件通知系统。一个服务端将会发送事件到客户端,客户端接收事件。客户端提供一个端点,以便服务端可以通过这个端点将消息发送到客户端。然后服务端使用端点来发送消息到客户端。下面代码显示了一个例子,一个客户端使用IDuplexChannel管道类型。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels;namespace WCFDuplexChannelClient {class Program{static void Main(string[] args){NetTcpBinding binding = new NetTcpBinding();BindingParameterCollection parameters = new BindingParameterCollection();Message m = Message.CreateMessage(MessageVersion.Soap12WSAddressing10,"uru:sendmessage");IChannelFactory<IDuplexChannel> factory = binding.BuildChannelFactory<IDuplexChannel>(parameters);IDuplexChannel channel = factory.CreateChannel(new EndpointAddress("net.tcp://localhost/sendmessage/"));channel.Send(m);channel.Close();factory.Close();}} }
请求响应通信
请求响应通信是一种特殊的双向通信,其中每个请求都有明确的响应,而且它总是由客户端发起。客户端发送了请求后,它就必须等待响应,然后才可以发送另一个请求。请求响应通信的通常用法是,从一个浏览器发送的一个HTTP请求。浏览器生成一个HTTP请求到服务端,如GET或POST,服务端处理请求,然后一个响应被回发。WCF使用IRequestChannel和IReplyChannel接口处理请求响应通行。如下图所示:
下面代码显示了一个客户端应用程序使用IRequestChannel来发送一个消息。注意,Request方法以返回值参数来返回响应消息。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels;namespace WCFRequestReplyChannelClient {class Program{static void Main(string[] args){BasicHttpBinding binding = new BasicHttpBinding();BindingParameterCollection parameters = new BindingParameterCollection();Message request = Message.CreateMessage(MessageVersion.Soap11,"urn:sendmessage");IChannelFactory<IRequestChannel> factory = binding.BuildChannelFactory<IRequestChannel>(parameters);IRequestChannel channel = factory.CreateChannel(new EndpointAddress("http://localhost/sendmessage/"));Message response = channel.Request(request);channel.Close();factory.Close();}} }
类型变更
内置的HTTP协议内在具有请求响应的性质,因此HTTP传输管道使用请求响应管道类型。其它的通信模式,如单向和双工,通过HTTP完成类型变更。通过分层协议管道顶端的传输管道,以支持单向或双工通信。下面的代码显示一个自定义的绑定,来分层一个单项类型管道绑定元素,OneWayBindingElement,在HTTP传输的顶端。后面的部分中,我们会看到使用CompositeDuplexBindingElement绑定元素实现类型变更的更优化的案例。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels;namespace WCFShapeChangingClient {class Program{static void Main(string[] args){CustomBinding binding = new CustomBinding(new OneWayBindingElement(),new TextMessageEncodingBindingElement(),new HttpTransportBindingElement());}} }
操作契约和管道类型
管道使用管道类型来实现对多种消息交换模式的支持。例如,一个基于TCP的传输管道将实现IInputChannel和IOutputChannel,因为这些传输本质上是单向模式。基于其它传输的其它协议,如TCP,可以实现多个管道类型。开发人员并不直接操作管道的类型。相反,WCF会基于一个服务的操作契约选择管道的类型。下面表格列出了你可以在操作契约中设置的各种属性以及结果管道类型。注意,大多数管道类型具有无会话状态和会话状态感知两种变化。会话感知管道从客户端向服务端传递一个标识符。这就可以在服务端和客户端之间维护会话状态。这类似于ASP.NET中的状态管理。WCF中没有状态管理功能,但是你可以使用会话状态与实例来管理状态。实例管理会在后面介绍。
不是所有的管道都实现了每个接口。如果基础管道不支持某个特定的管道类型,WCF将尝试适配一个存在的管道类型以满足需要。例如,如果一个单向管道没有实现IInputChannel和IOutputChannel接口,,WCF将会尝试使用IDuplexChannel或IRequestChannel/IReplyChannel实例。
管道监听
管道监听器构成了WCF中基本的服务端通信。他们负责监听进入的消息,创建管道栈以及提供指向应用程序栈顶的引用。他们从传输管道或从管道栈中的管道中接收消息。大多数开发人员不会直接操作监听器。他们使用ServiceHost类来宿主那些使用管道监听器监听消息的服务。我们会在介绍宿主时详细介绍ServiceHost类。下面代码显示了一个管道监听器被创建用来接收消息。绑定的BuildChannelListener方法基于特定类型的管道构建一个管道监听器。这个例子中,我们使用BasicHttpBinding和IReplyChannel类型。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels; using System.Runtime.Serialization;namespace WCFChannelListenersServer {class Program{static void Main(string[] args){BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.None);Uri address = new Uri("http://localhost/request");BindingParameterCollection bpc = new BindingParameterCollection();Console.WriteLine("Starting service...");IChannelListener<IReplyChannel> listener = binding.BuildChannelListener<IReplyChannel>(address, bpc);listener.Open();IReplyChannel channel = listener.AcceptChannel();channel.Open();Console.WriteLine("Service started!");Console.WriteLine("Waiting for request...");RequestContext request = channel.ReceiveRequest();Message message = request.RequestMessage;string data = message.GetBody<string>();Message replymessage = Message.CreateMessage(message.Version,"http://localhost/reply",data);request.Reply(replymessage);Console.WriteLine("Service stopped!");message.Close();request.Close();channel.Close();listener.Close();Console.ReadLine();}} }
管道工厂
管道工厂创建一个管道来发送消息并维护它创建的管道所有权。大多数开发者从不会直接使用管道工厂。相反,他们会使用一个类,继承自ClientBase<>,这个类通常由svcutil.exe或添加服务引用操作生成。然而,重要的是要了解管道工厂,因为他们构成WCF客户端通信的基础。
提示,管道工厂拥有它们的管道。管道监听器和工厂之间最重要的区别是管道工厂负责关闭所有相关联的管道;管道监听器不是。这种区别使管道监听器可以独立的关闭他们依赖的管道。
下面代码显示了使用一个管道工厂来调用服务。这是在上面例子中服务的客户端。这段代码使用绑定的CreateChannel方法来创建一个新的管道。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels; using System.Runtime.Serialization;namespace WCFChannelFactoryClient {class Program{static void Main(string[] args){BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.None);IChannelFactory<IRequestChannel> factory = binding.BuildChannelFactory<IRequestChannel>(new BindingParameterCollection());factory.Open();IRequestChannel channel = factory.CreateChannel(new EndpointAddress("http://localhost/request"));channel.Open();Message requestMessage = Message.CreateMessage(MessageVersion.Soap11,"http://chinasofti.com/reply","This.is the body data");Console.WriteLine("Sending message...");Message replymessage = channel.Request(requestMessage);string data = replymessage.GetBody<string>();Console.WriteLine("Reply received!");requestMessage.Close();replymessage.Close();channel.Close();factory.Close();Console.ReadLine();}} }
ChannelFactory<>
WCF中的两个类代表管道工厂:ChannelFactory和ChannelFactory<>。他们可能看起来比较相似,但是实际上他们做不同的事情。ChannelFactory<>类用在高级的情况下,如多个客户段需要创建时。本质上它与一个给定的ChannelFactory一起工作,但是它不负责创建管道栈。ChannelFactory<>类用来使用一个特定的服务契约类型定义一个类。下面代码显示一个案例,其中一个使用ChannelFactory<>类调用一个实现了IStockQuoteService接口的服务端。
提示,使用状态机制和ChannelFactory<>。使用状态机制关闭ChannelFactory时要小心。下面代码显示了在服务调用代码的外围使用try…catch以便从服务端抛出的错误都能被捕获。如果我们不使用try…catch,任何异常都有可能在使用过程中冒出来。从这点上来说,管道工厂应该在它关闭后抛出异常。这有可能使之前的错误从服务调用中暴露出来。我们使用两个try…catch块,使我们可以从服务调用中抓住任何异常。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels;namespace WCFChannelFactoryClassClient {class Program{static void Main(string[] args){try{using (ChannelFactory<IStockQuoteService> cf = new ChannelFactory<IStockQuoteService>()){IStockQuoteService service = cf.CreateChannel();try{double value = service.GetQuotr("ChinaSofti");}catch (Exception ex){//check exception from call to GetQuoteConsole.WriteLine(ex.ToString());}}}catch (Exception ex){//check exception for creating channelConsole.WriteLine(ex.ToString());}Console.ReadLine();}} }
ICommunicationObject
WCF中,ICommunicationObject接口是所有通信对象的基础(管道、管道工厂、管道监听等等)。开发人员要想定义自定义管道或者直接操作管道时,就需要了解这个接口。WCF中的通信对象需要实现一个特殊的状态机。这个状态机表现了所有通信对象运行的状态。这种方法类似于其它对象(如sockets).ICommunicationObject接口的目的是实现状态机。这允许WCF对所有通信对象形同看待,并抽象他们的基本实现。
下面代码显示了状态机制中提供的通信状态枚举。
public enum CommunicationState {Created, Opening, Opened, Closing, Closed, Faulted }
CommunicationState枚举列出了通信对象的六个状态。所有通信对象的初始状态是Created。当通信对象被实例化时,这个状态表明该通信对象进入系统。所有通信对象的最终状态是Closed。沿着这个方式,ICommunicationObject接口上的方法被一一调用,从而将通信对象从一个状态转换到另一个状态。例如,Open方法被调用,通信对象的状态从Created状态转换到了Opened状态。下图展示了一个状态图以表明通信对象在状态与状态之间的变化。
通信对象的一个例子就是ClientBase<>类,这个类是从添加服务引用操作或svcutil.exe生成客户端代码时的基本实现类。
注意,不能重复使用客户端。当一个通信对象已经从Opened状态转换到Closing或Faulted状态后,就不能回滚。这就意味着,通信对象不能返回Opened状态,除非首先重复创建这个通信对象。因此,客户端需要在他们被关闭后,重新创建。
五个事件(Opening,Opened,Closing,Closed和Faulted)是ICommunicationObject支持的方法。这些事件用来标示代码状态的转换。
提示,客户端通知。通常情况下应用程序会维护一个客户端代理的引用。这种情况下,使用状态转换事件,当客户端代理进入Faulted状态时用作通知(并最终转换到Closed状态)使客户端和服务端之间的通信能够维持下去。
ICommunicationObject接口通常用来将一个现有通信对向转换为接口类型,以获得访问ICommunicationObject中暴露的方法和事件。然而,其它时候,你想要创建一个新的通信对象来扩展WCF的能力。在这种情况下,WCF提供一个抽象基类调用CommunicationObject,它提供了ICommunicationObject接口的实现以及状态机制的关联。下面代码显示一个由svcutil.exe生成的StockQuoteServiceClient。这个客户端继承自ClientBase<>类。代码显示这个客户端被转化为ICommunicationObject接口,以便我们可以访问通信事件。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Channels;namespace WCFICommunicationObjectClient {class Program{static void Main(string[] args){string symbol = "ChinaSofti";double value;StockQuoteServiceClient client = new StockQuoteServiceClient();ICommunicationObject commobj = (ICommunicationObject)client;commobj.Closed += new EventHandler(commobj_Closed);commobj.Faulted += new EventHandler(commobj_Faulted);value = client.GetQuote(symbol);Console.WriteLine("{0} @ $ {1}",symbol,value);Console.ReadLine();}static void commobj_Faulted(object sender, EventArgs e){//Handle Closed Event}static void commobj_Closed(object sender, EventArgs e){//Handle Faulted Event}} }