整个可靠会话的机制是完全在信道层实现的,而整个信道层的最终缔造者就是绑定,所以可靠会话编程是围绕着绑定进行的。《上篇》对实现可靠会话的绑定元素已经如何使用系统绑定实现可靠会话进行了介绍,下篇将和你探讨WCF可靠会话编程模型余下两个主题:自定义绑定和对消息传递的强制约束。
一、为自定义绑定的可靠会话进行设置
绑定是一系列绑定元素的有序组合,但是系统绑定为我们提供适应了某种典型通信环境的绑定元素组合方式,可以看成是“套餐”。但是,如果套餐不符合您的胃口,你应该查看菜单点你喜欢的菜肴。自定义绑定给了你最大的自由度,是能能够根据具体的通信环境自由组合需要的绑定元素。
关于可靠会话,如果你采用系统绑定,你定制的范围其实很窄(仅限于InactivityTimeout和Ordered属性)。但是,如果你采用自定义绑定,由于你操作的对象就是ReliableSessionBindingElement绑定元素,所有你可以对所有的选项进行自由配置。虽然我们可以通过编程的方式之间将创建的ReliableSessionBindingElement对象添加到绑定的绑定元素集合中,但是我们还是强烈建议你通过配置的方式来对可靠会话的相关选项进行定制。为了让读者能够了解某个特性的配置,我个人觉得最好的办法就是直接让读者看看相关配置节的定义。WCF将ReliableSessionBindingElement的配置定义在如下所示的ReliableSessionElement类型中。通过ReliableSessionElement,你不但可以了解可靠会话相关的配置属性,还可以了解到其他相关的配置信息,比如最大值、最小值和默认值等。你可以验证一下它们是否和我们前面的介绍一致。
1: public sealed class ReliableSessionElement : BindingElementExtensionElement
2: {
3: [ConfigurationProperty("acknowledgementInterval", DefaultValue="00:00:00.2"), TypeConverter(typeof(TimeSpanOrInfiniteConverter)), ServiceModelTimeSpanValidator(MinValueString="00:00:00.0000001")]
4: public TimeSpan AcknowledgementInterval { get; set; }
5: public override Type BindingElementType { get; }
6: [ConfigurationProperty("flowControlEnabled", DefaultValue=true)]
7: public bool FlowControlEnabled { get; set; }
8: [ServiceModelTimeSpanValidator(MinValueString="00:00:00.0000001"), TypeConverter(typeof(TimeSpanOrInfiniteConverter)), ConfigurationProperty("inactivityTimeout", DefaultValue="00:10:00")]
9: public TimeSpan InactivityTimeout { get; set; }
10: [IntegerValidator(MinValue=1, MaxValue=0x4000), ConfigurationProperty("maxPendingChannels", DefaultValue=4)]
11: public int MaxPendingChannels { get; set; }
12: [IntegerValidator(MinValue=1), ConfigurationProperty("maxRetryCount", DefaultValue=8)]
13: public int MaxRetryCount { get; set; }
14: [ConfigurationProperty("maxTransferWindowSize", DefaultValue=8), IntegerValidator(MinValue=1, MaxValue=0x1000)]
15: public int MaxTransferWindowSize { get; set; }
16: [ConfigurationProperty("ordered", DefaultValue=true)]
17: public bool Ordered { get; set; }
18: protected override ConfigurationPropertyCollection Properties { get; }
19: [TypeConverter(typeof(ReliableMessagingVersionConverter)), ConfigurationProperty("reliableMessagingVersion", DefaultValue="WSReliableMessagingFebruary2005")]
20: public ReliableMessagingVersion ReliableMessagingVersion { get; set; }
21: }
在对自定义绑定进行配置的时候,我们只需要在绑定元素集合中添加ReliableSessionElement配置元素,并对相应的配置属性进行设置即可。下面的XML是服务端的WCF配置,我们采用自定义绑定作为终结点绑定。该自定义绑定由三个绑定元素组成,通过TextMessageEncodingElement对消息进行基于文本编码;通过HttpTransportBindingElement采用HTTP协议进行传输;在两者之间的ReliableSessionBindingElement实现了对消息的可靠传输。在bindings/binding/reliableSession配置节中,我对所有的属性进行的显式设置。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <bindings>
5: <customBinding>
6: <binding name="reliableSessionBinding">
7: <textMessageEncoding />
8: <reliableSession acknowledgementInterval="00:00:02" flowControlEnabled="false" inactivityTimeout="00:20:00" maxPendingChannels="8" maxRetryCount="4" maxTransferWindowSize="16" reliableMessagingVersion="WSReliableMessaging11" />
9: <httpTransport />
10: </binding>
11: </customBinding>
12: </bindings>
13: <services>
14: <service name="Artech.MessageInspection.Receiver.CalculatorService">
15: <endpoint address="http://www.artech.com/calculatorservice"
16: binding="customBinding" bindingConfiguration="reliableSessionBinding"
17: contract="Artech.MessageInspection.Receiver.ICalculator" />
18: </service>
19: </services>
20: </system.serviceModel>
21: </configuration>
看到这里,我想有的读者会问这么一个问题:一个绑定可以有一系列绑定元素构成,那么ReliableSessionBindingElement应该置于何处呢?要搞清楚这个问题,需要对WCF的绑定模型有一个大致的了解。绑定的目的创建一个用于处理和传输消息的信道栈,信道在信道栈的顺序决定于对应的绑定元素的排列顺序。可靠会话将客户端和服务端通过ReliableSessionBindingElement创建的可靠信道作为分界线,并在它们之间提供消息可靠传输保障。也就是说,信道栈中位于可靠信道之下(靠近传输信道)部分存在于可靠会话的势力范围之中,而上面部分则脱离可靠会话的管辖范围。这也是在前面给出的实例中为何我们将用于模拟不可靠网络的绑定元素配置到ReliableSessionBindingElement之下的原因所在。
由于在实际的应用中,我们主要通过可靠会话为网络传输的不稳定性提供可靠传输的保障,所以我们一般将ReliableSessionBindingElement配置到传输绑定元素之上。至于消息编码绑定元素,是置于ReliableSessionBindingElement之上或者之下均没有关系。如果你认真阅读过《WCF技术剖析(卷1)》第3章,你会知道消息编码绑定元素并不参与信道的创建,而是将编码的方式传入绑定上下文,传输信道据此采用相应的编码方式进行消息的编码或者解码。此外,为了,保证可靠会话的安全性,我们需要将可靠会话绑定到一个通过安全会话信道提供的安全上下文中。在这种情况下,ReliableSessionBindingElement需要位于安全绑定元素之上。
我们说WCF可靠会话编程完全就是围绕着绑定进行的,可以说得更加具体点,是围绕着ReliableSessionBindingElement进行的。除了对系统绑定或者自定义绑定进行设置,关于可靠会话编程模型,还涉及到一个契约行为:DeliveryRequirementsAttribute,我们可以利用DeliveryRequirementsAttribute对服务契约进行一些基于可靠会话的强制性约束。
二、通过DeliveryRequirementsAttribute对可靠会话进行强制约束
DeliveryRequirementsAttribute这个自定义特性,实际上是一个契约行为。我们可以将其应用到服务契约类型或者服务类型上,强制要求相应终结点绑定必须满足设定的关于消息传输方面的要求。DeliveryRequirementsAttribute定义如下,其中忽略了实现IContractBehavior接口的4个方法。
1: [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple=true)]
2: public sealed class DeliveryRequirementsAttribute : Attribute, IContractBehavior, IContractBehaviorAttribute
3: {
4: //其他成员
5: public QueuedDeliveryRequirementsMode QueuedDeliveryRequirements { get; set; }
6: public bool RequireOrderedDelivery { get; set; }
7: public Type TargetContract { get; set; }
8: }
DeliveryRequirementsAttribute定义了两个属性QueuedDeliveryRequirements和RequireOrderedDelivery,分别代表相应的终结点绑定必须满足的两个要求:队列传递和有序交付。队列传递及采用消息队列(即MSMQ)的机制进行消息传递,在下一个系列中我们会对队列服务进行单独介绍。而有序交付就是本章涉及的可靠消息传输的有序交付。IContractBehavior属性是对IContractBehaviorAttribute的实现,当我们将DeliveryRequirementsAttribute特性应用到某个实现了多个服务契约的服务上时,可以指定设置的消息传递要求是针对某个服务契约。
在服务端,当基于服务类型创建的ServiceHost对象被开启的时候,如果相应终结点绑定无法满足通过将DeliveryRequirementsAttribute特性应用到服务契约类型或者服务类型上设置的关于队列传输或者有序交付的要求时,会抛出一个InvalidOperationException异常。如果将DeliveryRequirementsAttribute特性应用到服务契约上,客户端在试图开启ChannelFactory<TChannel>对象的时候,同样会验证用于服务调用的终结点绑定是否满足相应的要求,如果无法满足,同样会抛出一个InvalidOperationException异常。
举个例子,假设我们定义如下一个IOrderService的服务契约用于处理订单。在IOrderService接口上,应用了DeliveryRequirementsAttribute特性并将RequireOrderedDelivery设置成True,要求终结点绑定强制开启可靠消息的有序交付特性。
1: [ServiceContract(Namespace = "http://www.artech.com/")]
2: [DeliveryRequirements(RequireOrderedDelivery = true)]
3: public interface IOrderService
4: {
5: [OperationContract]
6: void Process(Order order);
7: }
现在我们对实现该服务契约的服务进行寄宿,相应的配置如下。从中我们可以看到,我们采用WS2007HttpBinding作为终结点的绑定。通过绑定配置,开启了可靠会话,但是将ordered属性配置成False。
1: ?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <bindings>
5: <ws2007HttpBinding>
6: <binding name="reliableSessionHttpBinding">
7: <reliableSession ordered="false" enabled="true" />
8: </binding>
9: </ws2007HttpBinding>
10: </bindings>
11: <services>
12: <service name="Artech.MessageInspection.Receiver.OrderService">
13: <endpoint address="http://127.0.0.1:3721/OrderService" binding="ws2007HttpBinding"
14: bindingConfiguration="reliableSessionHttpBinding" contract="Artech.MessageInspection.Receiver.IOrderService" />
15: </service>
16: </services>
17: </system.serviceModel>
18: </configuration>
当进行服务寄宿的时候,你会得到如图1所示的InvalidOperationException异常。当你进一步看清具体的异常消息的时候,你可能第一感觉就是作者把图片弄错了。因为终结点绑定部满足DeliveryRequirementsAttribute设定的关于有序交付的要求,和队列传递根据就不相关。但是图1就是真实运行后的截图,这是WCF自身的一个Bug。在《WCF 中关于可靠会话的BUG!!》这篇文章中有对该Bug的原因的深入探讨。
到此为止,关于WCF可靠会话编程方面的内容就到此为止,下一篇我们对可靠会话架构体系进行进一步的挖掘,对可靠会话具体的实现原理进行深入剖析,敬请期待。