深入剖析WCF的可靠会话

本系列先后通过《实例篇》、《概念篇》、《协议篇》和《编程篇》对WCF的可靠会话进行了详细探讨。作为本系列的最后一片,我们将深入到WCF的可靠会话体系的最底层,对实现可靠会话的实现原理进行深入剖析。如果读者仔细阅读本系列博文,相信会使读者对可靠会话的理解提升到一定的高度。

从《编程篇》中,我们不难看出可靠会话的编程仅仅围绕着一个对象,那就是绑定。绑定在整个WCF架构模型具有重要的地位。WCF整个架构模型由两部分构成,即服务模型(Service Model)层和信道(Channel)层,而绑定是信道层的缔造者,同时也是连接两个层次的纽带。对可靠会话的实现,是完全在信道层实现的

绑定是由一系列绑定元素的有序组合,不同的保定元素具有各自的目的,而实现可靠会话的是一个叫做ReliableSessionBindingElement的绑定元素。在《WCF技术剖析(卷1)》的第3章对绑定模型的介绍中我们知道,绑定元素的主要任务是用于对信道管理器(Channel Manager)的创建。具体来说,在客户端和服务端分别创建信道工厂(Channel Factory)和信道监听器(Channel Listener)。由所有信道工厂和信道监听器创建的信道按照其创建者的顺序构建起一个消息处理的通道。

WCF通过最终通过ReliableSessionBindingElement创建可靠会话信道(Reliable Session Channel,以下简称RS信道),提供对可靠消息传输的实现。在WS-RM定义的可靠消息传输模型中,可靠消息传输是在RM源和RM目的地之间进行的,在这里,你可以将客户端和服务端的RS信道看成是RM源和RM目的地。

一、可靠会话信道层模型

图1反映的是可靠会话在信道层的实现模型,从中我们可以看出可靠会话建立在客户端和服务端的RS信道之间。作为客户端或者服务端信道栈中的一员,RS信道在信道栈中位置由ReliableSessionBindingElement在绑定元素集合中的位置决定。由于RS信道作为RM源和RM目的地存在,所以WCF中的可靠消息传输保障存在于客户端和服务端的RS信道之间。这其中不仅仅包括村存在于客户端和服务端之间的传输网络,也包括存在可靠会话信道之下的所有信道。相信大家还记得《实例篇》中的那个实例演示,我们用于模拟不稳定网络环境的信道对应的绑定元素最终配置在可靠会话绑定元素之后。如果将其移到可靠会话绑定元素之前,由该自定义信道导致的消息丢失的问题将不能通过可靠会话解决。

clip_image002

图1  可靠会话信道层模型

WS-RM为可靠消息传输的体现定义了一个可扩展的消息交换模型,而WCF的可靠会话时对该模型具体的实现。接下来,我们看看WCF的可靠会话是如何实现定义在WS-RM中的每一个消息交换步骤的。WCF目前支持WS-RM 1.0和1.1两个版本,在这里我们基于的是WS-RM 1.1。

二、CreateSequence 和CreateSequenceReponse

基于WS-RM的可靠消息传输是在一个RM序列中进行的,RM序列为实现可靠消息传输提供了一个上下文环境。对于WCF来说,与RM序列对应的概念就是可靠会话。基于WS-RM可靠消息传输从RM序列的创建开始,对于WCF来说,实现可靠消息传输需要首先创建可靠会话。WCF的可靠会话创建于可靠信道开启之时。站在编程人员的角度,当服务代理对象开启的时候,信道被打开。编程人员可以调用ICommunicationObject接口的Open方法显式地开启服务代理。如果服务代理在没有被显式开始的情况下被用于进行服务调用,WCF会对其进行隐式开启。

WS-RM中序列创建过程从RM源向RM目的地发送主体包含CreateSequence元素的消息(以下简称CreateSequence消息)开始,到接收到对方返回的主体包含CreateSequenceResponse元素的回复消息(以下简称CreateSequenceResponse消息)作为结束。对于WCF可靠会话来说,客户端和服务端信道栈中的RS信道充当着RM源和RM目的地的角色。当客户端RS信道开启的时候,它会创建CreateSequence消息,并沿着信道栈路径发送到服务端。该CreateSequence消息被服务端信道栈接收并最终递交给RS信道后,RS负责创建RM序列。序列创建成功后,可靠会话上下文在服务端部分被成功创建起来,被创建的RM序列被封装到CreateSequenceResponse消息中返回到客户端。当客户端RS信道接收到CreateSequenceResponse消息后,创建可靠会话上下文在客户端部分,至此,以两个RS信道的客户端会话被创建出来。

WS-RM中某个RM序列只能保证单向的消息传输的可靠性,也就是说,确保从终结点A到B的可靠消息传输的RM序列不能提供从终结点B到A的可靠消息传输保障。要想解决这种双向(Two-Way)可靠消息传输,需要借助于两个RM序列。所以,对于非单向(One-Way)消息交换模式,请求|回复模式和双工模式下的可靠消息传输需要双RM序列的支持。WS-RM通过序列提供(Sequence Offering)的机制对此提供支持。接下来,我们来讨论WCF的可靠会话对WS-RM序列提供机制的实现。

在客户端RS信道开启时,RS信道会先检测当前终结点服务契约中所有服务操作采用的消息交换模式。如果所有操作均采用单向消息交换模式(通过应用在操作方法上的OperationContractAttribute的IsOneway属性判断),RS将不会采用序列提供机制。表现在消息交换上面,就以为着CreateSequence消息的不会包含Offer元素。反之,如果服务契约包含任何一个非单向操作,RS信道会在客户端创建入栈序列(Inbound Sequence),并将其作为提供序列封装在CreateSequence消息的Offer元素中。下面的XML片断就是这样一个包含提供序列的CreateSequence消息。需要注意的是,在RS信道生成的CreateSequence消息中,Offer/Endpoint和AcksTo的终结点引用是相同的



    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">

      <s:Header>

        <a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-rx/wsrm/200702/CreateSequence</a:Action>

        <a:MessageID>urn:uuid:f41d4443-fa5c-4f9d-95ff-96159c96ebec</a:MessageID>

        <a:To s:mustUnderstand="1">http://www.artech.com/calculatorservice</a:To>

      </s:Header>

      <s:Body>

        <CreateSequence xmlns="http://docs.oasis-open.org/ws-rx/wsrm/200702">

          <AcksTo>

           <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>

         </AcksTo>

         <Offer>

           <Identifier>urn:uuid:25b6383f-b5a1-4839-8249-b0f273a7f502</Identifier>

           <Endpoint>

             <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>

           </Endpoint>

           <IncompleteSequenceBehavior>DiscardFollowingFirstGap</IncompleteSequenceBehavior>

         </Offer>

       </CreateSequence>

     </s:Body>

   </s:Envelope>


当包含Offer元素的CreateSequence消息被服务端RS信道成功接收到之后,会分析该元素封装的客户端提供的RM序列,如果满足要求的话,它会选择“接受”该序列。当客户端提供的序列的接受,体现在服务端RS信道会在CreateSequenceResponse消息中添加一个Accept元素,下面的XML就是这样一个CreateSequenceResponse消息。

    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">

      <s:Header>

        <a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-rx/wsrm/200702/CreateSequenceResponse</a:Action>

        <a:RelatesTo>urn:uuid:f41d4443-fa5c-4f9d-95ff-96159c96ebec</a:RelatesTo>

      </s:Header>

      <s:Body>

        <CreateSequenceResponse xmlns="http://docs.oasis-open.org/ws-rx/wsrm/200702">

          <Identifier>urn:uuid:97182c7f-ca5a-4c5f-8e17-6509387fa8bf</Identifier>

          <IncompleteSequenceBehavior>DiscardFollowingFirstGap</IncompleteSequenceBehavior>

         <Accept>

           <AcksTo>

             <a:Address>http://www.artech.com/calculatorservice</a:Address>

           </AcksTo>

         </Accept>

       </CreateSequenceResponse>

     </s:Body>

   </s:Envelope>


三、 Sequence和SequenceAcknowledgement

当RM会话成功建立起来之后,相同于在客户端和服务端的RS信道之间建立起了一个(对于所有操作均是单向的情况)或者两个(操作列表中具有至少一个非单向的操作)RM序列。此后,当应用级别的消息通过传入发送端(可能是客户端,也可能是服务端)信道栈抵达RS信道的时候,RS信道会为之添加一个基于WS-RM的Sequence报头。

同ASP .NET的会话一样,WCF中的可靠会话实际上也可以看成是一种状态保持机制,它将客户端的服务调用请求关联到RM序列这样一个上下文中。可靠会话不但保持着创建的RM序列的标识,还保持一个计数器保存在会话生命周期内发送出的消息数量,该消息数量也就是消息的序号

在发送端RS信道添加的Sequence报头中,按照WS-RM的规定包含RM序列的标识和消息的序号。下面的XML片断为你展示的就是我们熟悉的计算服务调用请求经过客户端RS信道后,整个请求消息的结构。

    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://docs.oasis-open.org/ws-rx/wsrm/200702" xmlns:a="http://www.w3.org/2005/08/addressing">

      <s:Header>

        <r:Sequence s:mustUnderstand="1">

          <r:Identifier>urn:uuid:2052a548-1505-4635-8995-a7c7e1e47379</r:Identifier>

          <r:MessageNumber>1</r:MessageNumber>

        </r:Sequence>

        <a:Action s:mustUnderstand="1">http://www.artech.com/ICalculator/Add</a:Action>

        <a:MessageID>urn:uuid:eacdc55d-eabf-4066-a958-5cc6753f1fe0</a:MessageID>

        <a:ReplyTo>

         <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>

       </a:ReplyTo>

       <a:To s:mustUnderstand="1">http://www.artech.com/calculatorservice</a:To>

     </s:Header>

     <s:Body>

       <Add xmlns="http://www.artech.com/">

         <x>1</x>

         <y>2</y>

       </Add>

     </s:Body>

   </s:Envelope>


当包含Sequence报头的消息被接收端信道栈的接收并将其递交给RS信道时,RS信道负责对接收到的消息进行确认。WCF可靠会话采用两种不同的确认机制,我个人将这两种确认机制命名为“单独确认”“背负(Piggy-Back)确认”。对于前者,接收端RS信道会和创建一个空的消息,并添加相应的确认报头。而对于后者,添加的确认报头直接将其放置到另一个消息中,这个消息可以是应用相关,也可以是应用无关(比如关闭、终止序列的消息),甚至可以是错误(Fault)消息。无论采用怎样的确认机制,接收端RS信道会在确认消息中添加SequenceAcknowledgement报头,并指定RM序列标识和确认消息序号范围。

一般来说,对于单向服务操作调用请求或者回调采用单独确认机制,而对于基于请求/回复模式的服务调用或者回调,会采用背负确认机制。单独确认机制机制和简单,在这里我们主要来谈谈背负确认。以请求|回复为例,假设在可靠会话情况下客户端通过如下的代码进行两次服务调用。

    using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))

    {

        ICalculator proxy = channelFactory.CreateChannel();

        (proxy as ICommunicationObject).Open();

        proxy.Add(1, 2);

        proxy.Add(1, 2);

        (proxy as ICommunicationObject).Close();

    }



当第一次调用Add方法的时候,服务端会接收到如上面XML所示的包含有Sequence报头的请求消息。当服务端RS接收到该请求时,并不会立即对其进行确认,而是利用回复消息进行确认。具体地说,当请求消息被分发给服务模型层并成功执行后,执行后的结果被封装成回复消息。当回复消息被传入信道层后会被RS信道接收,此时它会将SequenceAcknowledgement报头添加到回复消息中。下面的XML片断展示了客户端最终接收到的回复消息。

    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://docs.oasis-open.org/ws-rx/wsrm/200702" xmlns:a="http://www.w3.org/2005/08/addressing">

      <s:Header>

        <r:Sequence s:mustUnderstand="1">

          <r:Identifier>urn:uuid:aeb0849b-cca9-4fc5-bdf4-06b6d4f2109b</r:Identifier>

          <r:MessageNumber>1</r:MessageNumber>

        </r:Sequence>

        <r:SequenceAcknowledgement>

          <r:Identifier>urn:uuid:2052a548-1505-4635-8995-a7c7e1e47379</r:Identifier>

          <r:AcknowledgementRange Lower="1" Upper="1"/>

         <netrm:BufferRemaining xmlns:netrm="http://schemas.microsoft.com/ws/2006/05/rm">8</netrm:BufferRemaining>

       </r:SequenceAcknowledgement>

       <a:Action s:mustUnderstand="1">http://www.artech.com/ICalculator/AddResponse</a:Action>

       <a:RelatesTo>urn:uuid:eacdc55d-eabf-4066-a958-5cc6753f1fe0</a:RelatesTo>

     </s:Header>

     <s:Body>

       <AddResponse xmlns="http://www.artech.com/">

         <AddResult>3</AddResult>

       </AddResponse>

     </s:Body>

   </s:Envelope>


从上面的XML我们看到,回复消息的SequenceAcknowledgement报头正是对请求消息的确认,因为消息序号范围是从1到1(Lower="1" Upper="1")。除了SequenceAcknowledgement报头之外,回复消息同样具有一个Sequence报头。原因很简单,可靠会话不仅仅保障请求消息的可靠传输,同样需要对回复消息的可靠传输提供保障。在前面已经讨论过了,可靠会话通过对WS-RM序列提供机制的实现,帮助实现消息传输的双向保障。回复消息的Sequence报头包含客户端提供的RM序列标识和消息在该序列中的序号。

那么回复消息又是如何被确认的呢?答案是通过第二次服务调用的请求消息。具体来说,当通过相同的服务代理第二次调用Add方法的时候,客户端RS信道会在请求消息上面添加SequenceAcknowledgement报头作为对上一次回复消息的接收确认。我想你会想象得到服务端最终接收到的请求消息具有怎样的结构,下面的XML就是这样一个消息。

    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://docs.oasis-open.org/ws-rx/wsrm/200702" xmlns:a="http://www.w3.org/2005/08/addressing">

      <s:Header>

        <r:SequenceAcknowledgement>

          <r:Identifier>urn:uuid:dc416e9a-2646-44ef-a289-0c008a2a3ba0</r:Identifier>

          <r:AcknowledgementRange Lower="1" Upper="1"/>

        </r:SequenceAcknowledgement>

        <r:Sequence s:mustUnderstand="1">

          <r:Identifier>urn:uuid:fda625fa-9db4-46c4-9688-76ae3bc288ea</r:Identifier>

          <r:MessageNumber>2</r:MessageNumber>

       </r:Sequence>

       <a:Action s:mustUnderstand="1">http://www.artech.com/ICalculator/Add</a:Action>

       <a:MessageID>urn:uuid:e79bcca9-ccc8-4e63-b893-22fc5adeb6d3</a:MessageID>

       <a:ReplyTo>

         <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>

       </a:ReplyTo>

       <a:To s:mustUnderstand="1">http://www.artech.com/calculatorservice</a:To>

     </s:Header>

     <s:Body>

       <Add xmlns="http://www.artech.com/">

         <x>1</x>

         <y>2</y>

       </Add>

     </s:Body>

   </s:Envelope>


和第一次服务调用的请求消息进行对比,第二次服务调用多了一个AcknowledgementRange报头实现对上一次回复消息的确认。你也应该想到,第二次服务调用请求会在回复消息中被确认。但是,聪明的读者应该会有另一个问题,最后一次服务调用的回复消息如何被确认呢?

在前面给出的服务调用代码中,在进行第二次服务调用之后服务代理就被关闭了。第二次服务调用的回复消息貌似没有被确认的机会了。实则不然,当关闭服务代理的时候,客户端RS信道会向服务端发送一个主体部分包含CloseSequence元素的消息(以下简称CloseSequence消息)请求对RM序列的关闭,而该CloseSequence消息包含SequenceAcknowledgement报头实现对客户端接收到的所有回复消息的确认。整个消息发送和确认的过程如图2所示。

clip_image004

图2 请求/回复模式下的消息确认实现

四、 CloseSequence和CloseSeqenceResponse

当基于某个服务代理的所有服务调用结束,客户端程序应该关闭该代理。在开启可靠会话的情况下,服务代理的关闭同时意味着对可靠会话的终止(Termination),反映在WS-RM上就是对RM序列的终止。在RM序列终止之前,还有一个额外的过程,即RM序列的关闭

服务代理的关闭反映在WCF信道层上就是对信道栈的关闭。当客户端RS信道被关闭时,它负责关闭可靠会话。具体来讲,它会按照WS-RM规范创建主体部分包含CloseSequence元素的消息(以下简称CloseSequence消息)。不过在发送CloseSequence消息之前,RS信道会等待所有已发消息的确认均已成功接收。如果在可靠会话生命周期内有消息发送,CloseSequence消息中还会包含最后一个消息的序号。如果此时还有回复消息或者回调消息等待确认,CloseSequence消息还包含对回复消息或者回调消息进行确认的SequenceAcknowledgement报头。在该SequenceAcknowledgement报头中具有一个Final元素表明这个最后一个确认。下面的XML片断展示了客户端RS信道生成的CloseSequence消息。

    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://docs.oasis-open.org/ws-rx/wsrm/200702" xmlns:a="http://www.w3.org/2005/08/addressing">

      <s:Header>

        <r:SequenceAcknowledgement>

          <r:Identifier>urn:uuid:d3e23ddf-7b6d-474f-9171-78bb76f4f977</r:Identifier>

          <r:AcknowledgementRange Lower="1" Upper="2"/>

          <r:Final/>

        </r:SequenceAcknowledgement>

        <a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-rx/wsrm/200702/CloseSequence</a:Action>

        <a:MessageID>urn:uuid:fa99c2cf-4910-4598-a8ac-0e5b73787cc8</a:MessageID>

       <a:To s:mustUnderstand="1">http://www.artech.com/calculatorservice</a:To>

     </s:Header>

     <s:Body>

       <r:CloseSequence>

         <r:Identifier>urn:uuid:04127209-14ce-4648-90a5-a8eca6006fd2</r:Identifier>

         <r:LastMsgNumber>2</r:LastMsgNumber>

       </r:CloseSequence>

     </s:Body>

   </s:Envelope>


有一点需要提醒读者的是,WS-RM规定RM源和RM目的地具有可以在某个时刻向对方发送对现有RM序列关闭或者终止的请求。但是,WCF仅仅对基于RM源序列终止或者关闭请求提供支持,也就是只有客户端的RS信道才能主动请求终止目前的可靠会话

CloseSequence消息被服务端的RS信道成功接收之后,它同样按照WS-RM规范生成主体部分包含CloseSequenceReponse元素的消息(以下简称CloseSequenceReponse消息),并回复给客户端。CloseSequenceReponse消息同样包含一个SequenceAcknowledgement报头提供对所有接收到的消息的确认。和CloseSequence消息一样,该SequenceAcknowledgement报头包含和一Final元素表示这是最后一个确认。下面的XML片断为你展示了服务端RS生成的CloseSequenceReponse消息。

    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://docs.oasis-open.org/ws-rx/wsrm/200702" xmlns:a="http://www.w3.org/2005/08/addressing">

      <s:Header>

        <r:SequenceAcknowledgement>

          <r:Identifier>urn:uuid:04127209-14ce-4648-90a5-a8eca6006fd2</r:Identifier>

          <r:AcknowledgementRange Lower="1" Upper="2"/>

          <r:Final/>

          <netrm:BufferRemaining xmlns:netrm="http://schemas.microsoft.com/ws/2006/05/rm">8</netrm:BufferRemaining>

        </r:SequenceAcknowledgement>

        <a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-rx/wsrm/200702/CloseSequenceResponse</a:Action>

       <a:RelatesTo>urn:uuid:fa99c2cf-4910-4598-a8ac-0e5b73787cc8</a:RelatesTo>

     </s:Header>

     <s:Body>

       <r:CloseSequenceResponse>

         <r:Identifier>urn:uuid:04127209-14ce-4648-90a5-a8eca6006fd2</r:Identifier>

       </r:CloseSequenceResponse>

     </s:Body>

   </s:Envelope>


五、 TerminateSequence和TerminateSequenceReponse

当客户端和服务端的RS信道完成了CloseSequence/CloseSequenceResponse握手之后,正式为可靠会话的终止展开新一轮的对话。可靠会话的终止从客户端RS信道向对方发送RM序列终止请求开始。具体来讲,客户端RS信道按照WS-RM规范生成一个主体部分包含TerminateSequence元素的消息(以下简称TerminateSequence消息),并发送给服务端。一般来说,TerminateSequence消息也会包含于CloseSequence消息一致的SequenceAcknowledgement报头。如果在可靠会话生命周期内有过消息发送,TerminateSequence消息中还应该包含最有一个发送消息的序号,并且该序号与CloseSequence消息中的一致。下面是一个由WCF客户端RS信道生成的TerminateSequence消息。

    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://docs.oasis-open.org/ws-rx/wsrm/200702" xmlns:a="http://www.w3.org/2005/08/addressing">

      <s:Header>

        <r:SequenceAcknowledgement>

          <r:Identifier>urn:uuid:d3e23ddf-7b6d-474f-9171-78bb76f4f977</r:Identifier>

          <r:AcknowledgementRange Lower="1" Upper="2"/>

          <r:Final/>

        </r:SequenceAcknowledgement>

        <a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-rx/wsrm/200702/TerminateSequence</a:Action>

        <a:MessageID>urn:uuid:651a09f7-795a-4331-9faf-1856f0975b47</a:MessageID>

       <a:To s:mustUnderstand="1">http://www.artech.com/calculatorservice</a:To>

     </s:Header>

     <s:Body>

       <r:TerminateSequence>

         <r:Identifier>urn:uuid:04127209-14ce-4648-90a5-a8eca6006fd2</r:Identifier>

         <r:LastMsgNumber>2</r:LastMsgNumber>

       </r:TerminateSequence>

     </s:Body>

   </s:Envelope>


当服务端RS信道接收到TerminateSequence消息之后,作为回复,它会按照WS-RM规范生成一个主体包含TerminateSequenceResponse元素的消息(以下简称TerminateSequenceResponse消息)并返回给客户端。一般来说,TerminateSequenceResponse消息具有于CloseSequenceResponse消息一样的SequenceAcknowledgement报头。下面就是一个通过服务端RS信道生成的TerminateSequenceResponse消息。

    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://docs.oasis-open.org/ws-rx/wsrm/200702" xmlns:a="http://www.w3.org/2005/08/addressing">

      <s:Header>

        <r:SequenceAcknowledgement>

          <r:Identifier>urn:uuid:04127209-14ce-4648-90a5-a8eca6006fd2</r:Identifier>

          <r:AcknowledgementRange Lower="1" Upper="2"/>

          <r:Final/>

          <netrm:BufferRemaining xmlns:netrm="http://schemas.microsoft.com/ws/2006/05/rm">8</netrm:BufferRemaining>

        </r:SequenceAcknowledgement>

        <a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-rx/wsrm/200702/TerminateSequenceResponse</a:Action>

       <a:RelatesTo>urn:uuid:651a09f7-795a-4331-9faf-1856f0975b47</a:RelatesTo>

     </s:Header>

     <s:Body>

       <r:TerminateSequenceResponse>

         <r:Identifier>urn:uuid:04127209-14ce-4648-90a5-a8eca6006fd2</r:Identifier>

       </r:TerminateSequenceResponse>

     </s:Body>

   </s:Envelope>


六、 可靠会话如何实现流控制(Flow Control)

以上的内容充分反映了WCF可靠会话对WS-RM可靠消息传输模型的实现,接下来我们谈谈一个在WS-RM可靠消息传输模型没有提及的主题:流控制。也就是说,流控制是WCF基于WS-RM规范的一个扩展,它的实现从一个方面反映所有WS-*规范一个普遍的特点:可扩展性。WS-*是一个建立在XML上的规范体系,每一个WS-*规范为实现相应的协议进行的消息交换以及消息本身定义了一套规范。这些规范不应该限制太死,而应当给予规范的实现者充分的自由度去扩展它实现一些额外的功能。

在前面已经说过,消息缓冲是WS-RM实现可靠消息传输的主要机制。消息缓冲机制反映在WCF的可靠会话上,就是客户端和服务端的RS信道各自拥有消息缓冲区,它们的大小即容纳消息的数量可以独立地进行配置。对于消息发送端来说,如果消息缓冲区已满,RS信道就不能处理从上层信道传来的消息,直到接收到某个已发消息的确认后对应的消息从缓冲区中移除;对于消息接收端来讲,如果缓冲区已满,RS信道则不能处理来此下层信道传来的消息,直到缓存的消息被成功交付后从缓冲区移除。

由于客户端和服务端RS信道维持的消息缓冲区是相互独立的,如果发送端的消息缓冲区远远大于接收端消息缓冲区的大小,就会导致消息在接收端出现阻塞的现象。如果我们将消息的传输比喻成一条河流的话,上面的场景就意味着河流的源头水量很大,但是下流河道很窄不能充分容纳从源头流流入的水量,这就必然导致河水泛滥。为了解决这个问题,WCF的可靠会话采用了流控制的机制。

实际上,流控制机制从实现上非常简单,我将其称为“接收端接收容量通知机制”。表现在消息交换上面就是消息的接收端在对接收消息进行确认的时候,会顺带将本地消息缓冲区还能容纳的消息数量一并放在确认消息中。消息发送端在接收到消息确认后,提取该值并确定是否继续发送消息。

在我们上面给出SequenceAcknowledgement消息中,细心的读者可能留意到了在SequenceAcknowledgement报头中具有一个在WS-RM规范没有提及的元素BufferRemaining。该元素携带的数字就是接收端消息缓冲区中还能继续接纳的消息数量,如下面的XML所示。

    <r:SequenceAcknowledgement>

      <r:Identifier>urn:uuid:04127209-14ce-4648-90a5-a8eca6006fd2</r:Identifier>

      <r:AcknowledgementRange Lower="1" Upper="2"/>

      <r:Final/>

      <netrm:BufferRemaining xmlns:netrm="http://schemas.microsoft.com/ws/2006/05/rm">8</netrm:BufferRemaining>

    </r:SequenceAcknowledgement>


由于WCF的可靠会话完全是在信道层实现的,而信道层就是由一系列用于处理消息的信道组成,所有从消息在信道层的交换可以帮助我们很容易地从本质上把握可靠会话的实现。在目前所有关于WCF的著作中,没有一本能够站在如此低层次地对可靠消息的实现进行剖析。作为一本深入剖析WCF实现机制的文章,我们还此基础上对其进行进一步的挖掘。


我们站在信道层的角度剖析了WCF为了实现可靠会话在信道层进行的一系列消息交换,或者说客户端和服务端的RS信道为了实现可靠消息传输所进行一轮又一轮的握手。这一切都是基于这样一个假设:两个RS信道均可以在适当的时机向对方发送消息,或者说两个RS信道之间是一个双工的通道

如果我们站在传输层看待这个问题,该假设对于TCP传输是成立的,但是对于HTTP来说就有点问题了。HTTP本身就是一个基于请求|回复消息交换模式的应用层网络协议,并不能对双工通信提供支持。而WCF通过WSDualHttpBinding实现的双工通信机制和NetTcpBinding支持的双工通信具有本质的区别。NetTcpBinding创建的传输通道就是一个双工的TCP连接,而WSDualHttpBinding创建的所谓的双工通道实际上是两个方向相反的HTTP连接。接下来我们主要讨论当我们采用基于HTTP绑定——WSHttpBinding(或者是WS2007HttpBinding)和WSDualHttpBinding)时,实现可靠会话所进行的通信方式。

一、WSHttpBinding V.S. WSDualHttpBinding

如果采用WSHttpBinding,最终创建的是一条从客户端到服务端的HTTP通道。在这种情况下,客户端RS信道和服务RS信道之间的多轮握手(CreateSequence/ CreateSequenceResponse、Sequence/ SequenceAcknowledgement、CloseSequence/CloseSequence和TerminateSequence/TerminateSequenceResponse)均是采用这样的消息交换方式:客户端将相应的消息通过HTTP请求的形式发送到服务端,相应的回复或者确认通过HTTP回复返回图1揭示了上述的几次握手在传输层上的实现,其中实线部分代表HTTP请求,虚线部分代表HTTP回复。

clip_image002

图1 可靠会话基于通过WSHttpBinding创建的单通道的消息交换

图1中我们可以和清晰地看到,CreateSequence/ CreateSequenceResponse、CloseSequence/CloseSequence和TerminateSequence/TerminateSequenceResponse完全是按照HTTP请求/HTTP回复的形式实现的在进行服务调用的时候,即使采用的单向消息交换模式,发送应用消息的请求依然会接收到一个包含SOAP消息的HTTP回复。服务端通过将确认消息方法每一个HTTP回复之中

之所以采用如上的方式的根本目的在于,WSHttpBinding创建的传输层通道是从客户端到服务端的一条HTTP连接。HTTP连接是一条单工通道,客户端和服务端总是扮演者请求者和回复者的角色,服务端不能主动联系客户端,此外无论是对RM序列创建、关闭和中指的回复,还是消息确认只能放在HTTP回复中。

但是,如果我们采用WSDualHttpBinding作为终结点绑定,情况就大不一样了。由于WSDualHttpBinding会创建两条HTTP连接构成一个所谓的双工通道,服务端可以随时联系到客户端,不需要将相应的回馈通过HTTP回复随带捎回去。借助于WSDualHttpBinding创建的双工通道,可靠会话的上述握手采用如下的消息交换方式:客户端通过HTTP请求将RM序列创建、终止请求以及携带Sequence报头的应用消息发送给服务端,并得到一个状态为202的空HTTP回复。而真正的回复和消息确认都通过另一个HTTP连接的HTTP请求返回给客户端的,而这些HTTP请求通过会得到一个状态为202的空HTTP回复

图2是对可靠会话消息交换在传输层的反映。可能你会觉得这和我们上面介绍的WS-RM消息交换模式不一致,没有了CloseSequence/CloseSequence握手,对于TerminateSequence请求也没有相应的TerminateSequenceResponse回复,这是因为WSDualHttpBinding支持的WS-RM版本是1.0,而不是我们上面介绍的1.1。除了上述的两点不同之前,还有一个不一样的地方:客户端在发送RM序列终止请求之前会发送一个携带Sequence报头的空消息,而对于包含在该空消息中的Sequence报头,除了包含消息序号之外,还具有一个额外的LastMessage元素表明这是RM序列终止前的最后一个消息。关于WS-RM 1.0,限于篇幅的因素,在本书中不可能再进行深入的介绍,有兴趣的读者可以参阅OASIS官方文档。

clip_image004

图2 可靠会话基于通过WSDualHttpBinding创建的双通道的消息交换

我们也可以从另外一种视角来看WSHttpBinding和WSDualHttpBinding对可靠会话的不同实现方式。对于WSHttpBinding创建的单向信道来说,客户端对于服务端是一个不可寻址(Non-Addressable)的终结点。也就是说,客户端不能主动向客户端发起请求,只能在客户端对自己发起请求时,被动地将相应的信息通过HTTP回复的形式返回到客户端。但是,对于WSDualHttpBinding创建的双工信道,情况就不一样了。双工通道是客户端和服务端成为了对等终结点,无论是服务端还是客户端,对于对方来说都是可寻址的(Addressable)。服务端可以在任何时候向客户端发起请求,将相应的信息通过HTTP请求的方式发送给客户端。

双工通道成就了可靠会话的“批量确认”机制。为了尽可能地降低网络流量,接收端RS信道接收到消息之后,并不会立即为该消息进行单独确认,而是会等待一定的时间(通过ReliableSessionBindingElement的AcknowledgementInterval属性设置),对之前接收到的消息进行批量确认。由于接收端RS信道接收到消息和发送确认有一定的延迟,我们也称这种机制为“延迟确认”。

二、单向模式(One-Way)V.S.请求|回复(Request|Reply)和双工(Duplex)模式

决定实现WCF可靠会话真正采用的消息交换还具有另外一个因素:消息交换模式。单向模式和请求|回复以及双工模式下,可靠会话采用的消息交换方式具有很大的不同。如果终结点服务契约中的所有操作均是单向的(通过OperationContractAttribute特性的IsOneway属性设置),对于可靠会话来说仅仅存在一个从客户端到服务端的RM序列。反映在序列的创建上就意味着在客户端RS生成的CreateSequence消息中并不存在Offer结点

从应用层次讲,单向操作意味着客户端向服务端发送消息而不会接收到任何回复。由于服务端不会有任何的应用消息从服务端返回到客户端,服务端的RS信道只能创建一个空的SequenceAcknowledgement消息对接收的消息进行确认。

如果终结点服务契约中的所有操作中具有一个以上的非单向操作,WCF可靠会话不仅仅需要保障消息从客户端到服务端的可靠性,也需要对服务端到客户端的消息传输提供保障,所以WCF可靠会话需要建立两个方向相反的RM序列。具体来说,可靠会话采用“序列提供”机制创建了双向的RM序列。在客户端RS信道生成CreateSequence之前现在本地创建一个RM序列,然后将该序列封装到CreateSequence消息的Offer元素中“提供”给服务端。服务端RS信道接收到CreateSequence消息之后,处理创建客户端请求的RM序列之外,还会接受(或者拒绝)提供的序列。

不同于单向模式下采用单独的SequenceAcknowledgement消息进行消息确认,在请求|回复模式下,为了尽量降低网络流量,可靠消息采用“背负(piggy-back)”机制实现消息确认。具体来说,客户端RS信道将SequenceAcknowledgement报头放到请求消息中,实现对接收到的回复消息的确认;服务端RS信道则将SequenceAcknowledgement报头放到回复消息中,实现对已经接收到的请求消息的确认。

而双工(Duplex)是由两个简单消息交换模式(单向或者请求|回复模式)组合而成,具体消息交换方式你应该可以上面接受推导出来,在这里就不再赘言讲述了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值