代码改变世界

WCF 消息交换模式

2011-07-28 10:57  田志良  阅读(...)  评论(... 编辑 收藏

  当设计消息应用系统的时候,有必要考虑一下消息是怎样在发送者、中介者和接受者流转的。系统中消息交换可能性的波动的值可以被不同程度地详细描述。这些不同级别的细节就是众所周知的消息交换模式(MEPS)、消息拓扑和消息编排。当从总体来看时,这三个级别的细节让我们抽象地描述任何消息场景。

  我们描述消息交换最常用的细节粒度就是消息交换模式(MEP)。根据W3C草案(http://www.w3.org/2002/ws/cg/2/07/meps.html),一个MEP是“一个描述在消息参与者之间交换的消息的模板。”一个MEP被限制在一个发送者和接收者之间的逻辑连接上,整个行业也已经接收了这个事实。因为MEPS是个有点抽象的概念,近距离看一些现实世界的例子对于我们弄清MEPs这个概念有帮助。让我们来看看下面我和朋友关于足球票通话的例子:

1.    我拿起电话,打给Rusty。
2.    Rusty拿起手机。
3.    Rusty说:Hello。
4.    我说:你看今天的比赛了吗?
5.    Rusty说:是的,踢得太烂了。真难相信我们没有赢。
6.    Rusty说:他们全部哑火了。
对话继续…

  1-3步可以认为是传输规范的事件(我呼Rusty,他接电话,准备通话)。第4步,我以问题的形式发给Rusty一个消息,习惯告诉我他应该回答。第5步,Rusty发送给我一个消息作为问题的回应。第6步是非主动请求的消息,发送给我,我可以回应或者不回应。这个消息的关联性是非常含蓄的,因为它是对话流的一部分。如果没有前后场景的关联,我会不知道他在说什么,这将会很糟糕。就像是一个推销的电话或者象棋比赛一样。

  在这个场景里,Rusty和我都可以随便说任何内容(就想步骤6里),我们也可以一个接一个不停地聊下去。一个单向的谈话也有可能,就像股东电话会议,或者因为一方终止了通话在回应发送以前,让我们看一下另外一个电话通话的例子:

1.    Lewis(我老板)拿起电话,拨我的手机号。
2.    我拿起手机说:“Hello”。
3.    Lewis说:“你干的太好了。我会立即给你100%的加薪,”。
4.    Lewis挂断了电话。
5.    我打回给Lewis。
6.    Lewis接电话。
7.    我说:“你太大方了,我只要50%就可以啦。”。
8.    我挂断电话。
9.    Lewis又打过来。
10.   我接听电话
11.   Lewis说:“100%加薪是最终结果,我现在正开一个蓝色保时捷911 Turbo赶过去,确保你能和我签约。”
12.   Lewis又挂了。

  在前面的场景里,我可以回应,但Lewis如此想给我加薪,以至于都没听我的答复。我只要再打给他进一步讨论详情。概念上说,消息交换里的响应需要发送者侦听在一个存在的连接或者一个新的连接上。

  通话可以使用更严格的模型。想想一下飞行员和机场控制塔台之间的对话,如果你曾经听过这些通信,结构显而易见:

1.    控制台呼叫飞行员:“Contoso437,转向180度,300哩(节/小时,飞行速度),下降到10000英尺。”
2.    飞行员回答:“Contoso437,正在转向180度,300哩(节/小时,飞行速度),正要下降到10000英尺”。

  这个场景里,控制塔变化请求,要求回复。如果飞机没有回复,罗嗦的控制台会重复这个指令直到一个收到响应为止,或者采取其他的一些行动。进一步说,这个协议要求飞行员当控制塔正在通信的时候不能打断塔台。

  这些简单的比喻对理解面向服务应用系统中的消息参与者之间交互很有帮助。概括地说,MEPS是根据参与者如何交互、更确切地说,允许回复的数量和发送者和接受者之间需要不需要新的连接来分类的。也许电话和无线通话有所不同,但是在面向服务的世界里通常有3种类型的MEPS:数据报、请求-应答和双工。

数据报交换模式

  图3-1说明了数据报消息交换模式。也会被称为simplex(单一模式),这个MEP表示一个单向的消息发送,或者即发即弃的发送。使用这个MEP的消息发送被称为数据报MEP。概念上说,一个数据报与电话捉迷藏游戏里的语音留言很相似。当你正在语音留言的时候,你可能不想立即得到回复,你也许希望以后回个电话。回复一个数据报消息被认为是带外的(out of band)。换句话说,就是需要发送者和接受者之间建立一个新的连接来响应数据报。

图3-1数据报消息交换模式

  1. 数据报和WSDL

  一个数据报MEP(消息交换模式)使用WSDL语言描述为一个包含wsdl:input 和没有wsdl:output元素的操作。例如,下面的WSDL代码块描述了一个可以接受输入的操作,因此是一个使用数据报MEP的操作。

<wsdl:portType name="ISomeContract"  >
    <wsdl:operation name="SomeOperation">
       <wsdl:input wsa10:Action="urn:SomeActionInput"   />
    </wsdl:operation>
</wsdl:portType>

  2. 数据报和WCF契约

  创建一个使用数据报MEP的WCF程序,是相当容易的。通常,我们从下面的契约开始:

// File: Contract.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
[ServiceContract(Namespace="http://wintellect.com/SomeContract",
 Name="ISomeContract")]
public interface ISomeContract {
 [OperationContract(Name="SomeOperation",
                     Action="urn:SomeActionInput",
                     IsOneWay=true)]
 void SomeOperation(Message message);
}

  这个例子里有2个重要的东西:SomeOperation方法的Void返回类型和OperationContractAttribute属性的的IsOneWay标记。

  Void返回类型 因为我们正在使用C#接口去描述一个数据报消息操作,我们需要去用一个返回类型来表示这是一个单向的操作。用来描述数据报消息操作的方法必须有一个Void返回类型。指定别的返回类型,WCF运行时在验证契约的时候会抛出一个InvalidOperationException异常。

  操作契约的IsOneWay属性 当定义一个数据报操作的时候,只定义一个带Void返回类型的接口是不够的。考虑下面的契约:

// File: Contract.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
[ServiceContract(Namespace="http://wintellect.com/SomeContract",
 Name="ISomeContract")]
public interface ISomeContract {
 [OperationContract(Name="SomeOperation",
                     Action="urn:SomeActionInput",
                     ReplyAction="urn:SomeActionOutput")]
 void SomeOperation(Message message);
}

  这个契约最后转换的WSDL描述如下:

 <wsdl:portType name="ISomeContract"  >
    <wsdl:operation name="SomeOperation">
      <wsdl:input wsaw:Action="urn:SomeActionInput"  />
      <wsdl:output wsaw:Action="urn:SomeActionOutput" />
    </wsdl:operation>
 </wsdl:portType>

  wsdl:output元素的出现表示应该有个消息回复发送者。因此我们接口方法的返回类型是无效的,消息会有一个空的消息体元素(<Body>)。尽管消息体力没有数据,仍然会有回复。重要的是要注意WCF运行时必须产生这个回复消息,并且每次收到一个有效消息的时候都需要处理工作。在WCF里,唯一避免回复消息的方法就是在操作属性里设置IsOneWay属性。默认的值是false,这个设置使得操作默认使用请求/应答MEP。

  3. 错误处理考虑

  数据报MEP涉及到了一个有趣的面向服务的错误处理的手法。错误可以被序列化为SOAP错误,并且这些错误可以被发送到指定的终结点。使用数据报MEP,发送者没有义务去侦听这些错误消息。如果发送者希望接受这些消息或者发送到其它的终结点,发送者必须在消息头块里的<FaultTo>指定地址。然后接受者会尽全力把错误消息发送给指定的终结点。

  4. HTTP和数据报MEP

  所有的传输都支持数据报消息交换模式,但是一些传输,像HTTP和HTTPS,已经有相对应的机制内建到传数里。当消息通过这些传输协议传输的时候,发送者希望接受一个响应,并且发送者希望发送一个响应。例如,当我们对一个页面发送HTTP请求的时候,我们希望得到一个HTTP回复。同样地,一个Web服务器希望在收到一个资源请求以后能够发送给客户端一个HTTP响应。HTTP响应是通过反向通道传输的。为了这个讨论,我们可以把反向通道理解为一个侦听器,它会再响应消息发送以后停止侦听。

  在WCF里,当我们通过HTTP发送一个数据报,我们通过HTTP发送数据,回复是一个HTTP202响应代码。通常来说,当一个数据报消息通过一个要求内建应答机制的传输来发送的时候,响应消息会包含一个传输规范规定的应答,而没有消息规范信息。

  通过HTTP接收数据报的WCF应用,会再处理数据报以前发送一个202应答。这个优化意味着客户端不需要不必要地等待传输应答,并且这个交换方式非常接近单向模式。

请求/响应MEP

  广义上说,Internet是建立在请求/响应消息交换模式上的(MEP,同样被称作半双工half-duplex)。我们希望一个单一的页面请求会产生一个HTML响应。如果我们希望看到另外一个页面,我们要发送新的请求。换句话说,给我们请求的响应是带内方式的(in band,和上面的带外out of band对应,这里指的是使用一个通道)。图3-2概念上说明了请求/响应MEP。

图3-2请求/响应MEP

备注:请求/响应消息交换模式隐藏的很深,所以当我们使用它的时候也很难注意的到。大部分情况,关于请求/响应模式,我们的经验会让我们条件反射式的思考。比如,我们的大部分基于组件的frameworks要求我们调用一个方法以后要等待方法返回。许多分布式frameworks(像COM)更是强化了这一点,因为这些frameworks允许我们去调用一个方法并且等待响应。我鼓励你去从这个缺省的MEP“自由思考”,强迫自己去思考一下WCF中可能存在的其它MEP。

  1. 请求/响应和WSDL

  请求/响应消息交换模式使用WSDL表述为一个包含wsdl:input 和wsdl:output元素的操作。例如,下面的WSDL代码块描述的就是一个请求/响应消息交换模式:

<wsdl:portType name="ISomeContract"  >
    <wsdl:operation name="SomeOperation">
      <wsdl:input wsaw:Action="urn:SomeActionInput"   />
      <wsdl:output wsaw:Action="urn:SomeActionOutput"   />
    </wsdl:operation>
</wsdl:portType>

  重要的是要注意代码里的wsdl:input 和wsdl:output元素。这些元素的顺序暗示了在一个响应消息发送以前,请求消息必须先被接收。

  2. 请求/响应和WCF契约

  WCF操作契约缺省使用请求/响应MEP。任何WCF认为可序列化的类型都可以指定为一个返回类型。比如,下面的契约使用请求/应答模式:

// File: Contract.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
[ServiceContract(Namespace="http://wintellect.com/SomeContract",
 Name="ISomeContract")]
public interface ISomeContract2 {
 [OperationContract(Name="SomeOperation",
                     Action="urn:SomeActionInput",
                     ReplyAction="urn:SomeActionOutput")]
 Message SomeOperation(Message message);
}

  一些传输,像用户报文协议(UDP)和MSMQ,本质上都是单向的。当WCF最初发布的时候,还没有对于使用MSMQ的请求/应答MEP的直接支持,同样也不支持UDP。通过MSMQ传输的请求/应答MEP需要一个像接受者和发送者之间的连接一样的连接。当然,一个自定义通道也是可以的。

  有几个可以定义接受者发送回复或者错误消息的WS-Addressing消息头块。当使用像TCP、HTTP或者命名管道这样的传输时,接受者可以通过反向通道发送响应消息。在这些场景里,WS-Addressing表示响应消息应该发送到<ReplyTo>消息头块定义的http://www.w3.org/2005/08/addressing/anonymous地址。这些请求消息里的内容如下:

<s:Envelope   >
 <s:Header>
     <a:Action s:mustUnderstand="1">urn:SomeActionRequest</a:Action>
     <a:MessageID>urn:12345</a:MessageID>
     <a:ReplyTo>
       <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
     </a:ReplyTo>
     <a:To s:mustUnderstand="1">
       net.tcp://localhost:8000/SomeOperation
     </a:To>
     </s:Header>
     <s:Body> </s:Body>
 </s:Envelope>

  响应消息如下:

<s:Envelope   >
 <s:Header>
   <a:Action s:mustUnderstand="1">urn:SomeContractReply</a:Action>
   <a:RelatesTo>urn:12345</a:RelatesTo>
   <a:To s:mustUnderstand="1">
     http://www.w3.org/2005/08/addressing/anonymous
   </a:To>
   </s:Header>
   <s:Body> </s:Body>
 </s:Envelope>

双工MEP

  双工是同时发送和接受消息,是一个我们在电话通话里见到的交互方式。在一个消息应用里,双工MEP定义了一个允许同时在接受者和发送者发送消息的操作的集合,反之亦然。图3-3说明了双工MEP

图3-3双工MEP

  1. 双工MEP和WSDL

  在双工消息交换模式里发送者和接受者可以自由地发送消息。与双工消息交换模式相关的WSDL包含2个操作。一个操作(SomeOperation)表示消息发送给接收者,另外一个操作(SomeCallbackOperation)表示消息从接受者回发给发送者。

<wsdl:portType name="ISomeContract"  >
    <wsdl:operation name="SomeOperation">
      <wsdl:input wsa10:Action="urn:SomeActionRequest"   />
    </wsdl:operation>
    <wsdl:operation name="SomeCallbackOperation">
      <wsdl:output wsa10:Action="urn:SomeCallbackRequest"   />
    </wsdl:operation>
 </wsdl:portType>

  某种意义上,双工MEP是其它消息交换模式(MEP)的结合。例如,前面的WSDL代码描述了2个操作,换句话说,一个消息报可以从发送者发送给接受者,反之亦然,消息也可以从接受者发送给发送者。也可能是消息在这些参与者通过请求/应答MEP来发送。思考一下下面的代码:

 <wsdl:portType name="ISomeContract">
    <wsdl:operation name="SomeOperation">
      <wsdl:input wsaw:Action="urn:SomeActionRequest"   />
      <wsdl:output wsaw:Action="urn:SomeContractReply"   />
    </wsdl:operation>
    <wsdl:operation name="SomeCallbackOperation">
      <wsdl:output wsaw:Action="urn:SomeCallbackContractRequest"   />
      <wsdl:input wsaw:Action="urn:SomeCallbackContractReply"   />
    </wsdl:operation>
 </wsdl:portType>

  SomeOperation操作描述了从发送者到接收者(urn:SomeActionRequest)和接收者回发给发送者(urn:SomeContractReply)的消息。SomeCallbackOperation操作表示从接受者到客户端(urn:SomeCallbackContractRequest)和回发给接受者(urn:SomeContractReply)的消息。

  2. 双工MEP和WCF契约

// File: Contract.cs
 
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
 
// the service contract looks the same as before, except
// for the addition of the CallbackContract property
// IsOneWay=true can also be set
[ServiceContract(Namespace="http://wintellect.com/SomeContract",
                 Name="ISomeContract",
                 CallbackContract=typeof(ICallbackContract))]
public interface ISomeContract3 {
 [OperationContract(Name="SomeOperation",
                     Action="urn:SomeActionRequest",
                     ReplyAction="urn:SomeContractReply")]
 void SomeOperation(Message message);
}
 
// No ServiceContract is necessary on the callback contract
// IsOneWay=true can also be set
public interface ICallbackContract {
 [OperationContract(Name="SomeCallbackOperation",
                     Action="urn:SomeCallbackContractRequest",
                     ReplyAction="urn:SomeCallbackContractReply")]
 void SomeCallbackOperation(Message message);
}

  注意一下回调契约被服务契约的ServiceContractAttribute类的CallbackContract属性引用。

提醒:当创建一个双工契约的时候,要记得标记操作为单向操作。如果OperationContractAttribute的IsOneWay 属性没有标记,消息交换模式会采用默认的请求/应答模式。2个参与者会为了创建回复消息而产生额外的开销。设置IsOneWay 属性为True可以减少每次消息交互的开销。