从基础架构的角度上看,WCF可以分为服务模型层和信道层两个层次,服务模型层建立在信道层的基础是上,而信道层就是通过本节即将介绍的binding绑定创建,注意这儿的绑定与.NET很多地方的绑定概念不同(例如最常见的数据绑定),注意理解。那么binding是如何创建信道层的呢?它通过组合不同的信道,将其整合为一个指定的信道栈,这个过程其实就是一个职责链模式的实现,每个信道都只处理自己的一部分内容,最基本的有传输、编码,复杂一些的包括事务流转、安全传输和可靠传输,使得整个框架足够灵活,已于扩展,一个支持WS-*的信道栈如下图所示。
其中传输信道实现了基于某种协议的消息传输,消息编码信道实现了消息的编码(例如XML、Binary、MTOM),而WS-AT(WS-Atomic Transaction)实现了分布式的事务支持,WS-RM(WS-Reliable Messaging)实现了信息的可靠传输,WS-Security实现了消息的传输安全,他们都可以被称为协议信道。接下来通过一个简单的例子来演示通过绑定进行消息通信,在其中将引入信道、信道监听器、信道工厂等主要对象。
通过这个例子看起来很像以前的Window网络编程中的Socket编程形式,首先服务端监听,然后客户端请求,服务端接收并绑定Socket(这儿是绑定信道),之后就可以在此基础上进行通讯了。这部分涉及到的类型很多,接下来通过一个表格简述部分主要类,浏览即可。
类别 |
介绍 |
信道与信道栈 |
最基础的ICommunicationObject接口,提供统一管理通信对象的状态机,可以作为一种设计范例用于实际项目中;DefaultCommunicationTimeouts类负责控制超时时限;IChannel和ChannelBase用于表示信道;ISession和ISessionChannel<TSession>用于表示会话信道。此外,支持3种消息交换模式。 数据报Datagram模式:一般使一部的消息发送方式,支持1或多个接收者,对应IOutputChannel, IInputChannel 请求-回复模式:对应IRequestChannel、IReplyChannel 双工模式:对应IDuplexChannel |
信道监听器(Server) |
IChannelListener, ChannelListenerBase |
信道工厂(Client) |
IChannelFactory, ChannelFactoryBase |
最后,进入绑定元素与绑定的介绍,之前提到过,绑定是用于创建信道栈的,而它其中的绑定元素则是用于创建具体的信道的。常见的系统绑定包括:BasicHttpBinding、WSHttpBinding、WS2007HttpBinding、WSDualHttpBinding、NetTcpBinding、NetNamedPipeBinding和NetMsmqBinding。其中BasicHttpBinding最为基础,在构建类似web服务形式的应用中使用最多,所有带Net前缀的绑定将局限于.NET平台,不同的绑定的运行效率有不小差异。一般来说,企业内部的服务推荐使用RPC类型的服务,如NetTcpBinding,而对外服务推荐使用WSHttpBinding,当然实际项目中,对外服务一般不会使用WCF框架,而是使用Restful风格的WebAPI。此外,也可以建立自定义的绑定,将框架提供的绑定元素进行重新组合,更有甚者,可以自定义绑定元素,不过这部分内容使用的场景非常的少。最后,提供一个简单自定义绑定配置作为参考,其组合了传输、编码和安全3个绑定元素,前两者是必选项,且必须按照顺序构建。
1 <bindings > 2 <customBinding> 3 <binding name="testBinding"> 4 <security></security> 5 <textMessageEncoding></textMessageEncoding> 6 <tcpTransport></tcpTransport> 7 </binding> 8 </customBinding> 9 </bindings>
契约其实就是一个生活中的概念,是一种双边和多边的协议,在WCF中,其保证了无论服务的实现有任何的改变,而服务的消费者始终可以通过契约约定方式来调用服务。由于整个WCF都是基于SOAP以及WS-*的,因此其XML是数据格式标准,通过XSD控制XML的数据结构,用WSDL(web服务描述语言)来提供跨平台的描述服务。
服务契约的定义通过ServiceContractAttribute和OperationContractAttribute两个特性来定义,前者定义整个服务,后者定义服务中具体的方法,接下来具体介绍一下这两个类。ServiceContractAttribute类,比较重要的属性包括:Name,可以定义服务的名称,默认为接口名;Namespace定义服务的命名空间,可以使用自己的公司名和项目名的组合来设定,其和之前的Name在wsdl文件中均是对<portType>元素的修饰;ConfigurationName实际上就对应配置中的Contract名称;SessionMode表示契约的会话模式,比如Allowed、Required等;ProtectionLevel表示消息的保护级别;CallbackContract在双工通信时指定回调操作的接口类型。OperationContractAttribute类,其属性Name、Namespace、ProtectionLevel与之前相似,值得一提的属性包括:Action/ReplyAction用于控制某个操作请求/回复信息的<Action>头,其默认通过命名空间、服务契约、操作名称组成,后者默认添加Response;IsOneWay控制消息交换的模式。提到消息交换的模式,记得之前提到过主要的三种请求-回复、单向和双工,前两项之前的例子中已有展示,之后的示例将展示双工模式。
1 服务端: 2 public interface IAddCallback 3 { 4 [OperationContract] 5 void DisplayResult(CompositeType result, CompositeType a, CompositeType b); 6 } 7 [ServiceContract(CallbackContract=typeof(IAddCallback))] 8 public interface IAddService 9 { 10 [OperationContract] 11 void Add(CompositeType a, CompositeType b); 12 } 13 14 [DataContract] 15 public class CompositeType 16 { 17 [DataMember] 18 public int PartA { get; set; } 19 [DataMember] 20 public string PartB { get; set; } 21 } 22 public class AddCallbackService : IAddCallback 23 { 24 public void DisplayResult(CompositeType result, CompositeType a, CompositeType b) 25 { 26 Console.WriteLine("x + y = {2} when x= {0} and y = {1}", a.PartA, b.PartA, result.PartA); 27 } 28 } 29 30 public class AddService : IAddService 31 { 32 public void Add(CompositeType a, CompositeType b) 33 { 34 var result = new CompositeType() { PartA = a.PartA + b.PartA, PartB = a.PartB + b.PartB }; 35 IAddCallback callback = OperationContext.Current.GetCallbackChannel<IAddCallback>(); 36 callback.DisplayResult(result, a, b); 37 } 38 } 39 40 配置: 41 <system.serviceModel> 42 <behaviors> 43 <serviceBehaviors> 44 <behavior name="metadataBehavior"> 45 <serviceMetadata httpGetEnabled="True" httpGetUrl="http://127.0.0.1:9901/addservice/metadata"/> 46 <serviceDebug includeExceptionDetailInFaults="true"/> 47 </behavior> 48 </serviceBehaviors> 49 </behaviors> 50 <services> 51 <service name="Sory.Entertainment.WCF.AddService" behaviorConfiguration="metadataBehavior"> 52 <endpoint address="net.tcp://127.0.0.1:1001/addservice" binding="netTcpBinding" contract="Sory.Entertainment.WCF.IAddService"/> 53 </service> 54 </services> 55 </system.serviceModel> 56 57 客户端: 58 59 InstanceContext callback = new InstanceContext(new AddCallbackService()); 60 using (DuplexChannelFactory<IAddService> channelFactory = new DuplexChannelFactory<IAddService>(callback, "addservice")) 61 { 62 var addChannel = channelFactory.CreateChannel(); 63 addChannel.Add(new CompositeType { PartA = 1 }, new CompositeType { PartA = 2 }); 64 } 65 66 配置: 67 <system.serviceModel> 68 <client> 69 <endpoint name="addservice" address="net.tcp://127.0.0.1:1001/addservice" binding="netTcpBinding" contract="Sory.Entertainment.WCF.IAddService"/> 70 </client> 71 </system.serviceModel>
当调用以上示例的服务时,会抛出一个关于死锁的异常,原因是其在并发场景下会造成回调死锁的情况,可以通过将请求或回调方法设置为单向即可。
此外,服务契约是不支持继承的,而操作契约支持继承,不过这部分也不太常用,而与契约相关的元数据描述类也非常简单,这儿就不展开介绍了。
-
多线程和异步操作
在《CLR via C#》中,将操作分为计算限制的和I/O限制的,一般来说,WCF中主要涉及到I/O限制的操作,这种类型的操作主要是通过异步模型来提高其并发性。谈到异步操作,在SOA这类应用中包含3个不同异步场景,这部分知识比较有意思,曾经困到鄙人多年。这3中场景包括:异步的信道调用,客户端可以通过代理对象异步的调用信道;单向消息交换,客户端的信道通过单向的消息交换模式向服务端发送消息,发送立刻返回;异步服务实现,服务端在具体实现服务操作时,采用异步调用的方式。
异步服务代理的创建,可以通过在添加服务引用时通过高级选项添加生成异步操作选项,之后可以通过使用BeginXX/EndXX方法、回调和事件注册等方式使用异步服务代理类。而异步的服务实现可以在服务接口中将原有方法修改为BeginXXX/EndXXX形式的异步方法名,并将OperationContract契约的AsyncPattern属性设置为true即可。
-
操作的选择与执行
之前提及的契约描述类中的Operations列表只包含了被OperationContractAttribute特性修饰的服务操作,而运行时的操作是通过DispatchOperation和ClientOperation两个类型表示。DispatchOperation在服务端的终结点分发器初始化时建立一个DispatchRuntime类,其通过一个SynchronizedKeyedCollection<string, DispatchOperation>集合类型来管理所有的运行时分发操作,OperationSelector用于操作选择,IOperationInvoker用于操作执行。ClientOperation和前者的结构基本一致,只不过它用于客户端而已。