选择消息交换模式

编写自定义传输的第一步是确定要开发的通道需要哪些 消息交换模式 (或 MEP)。 本主题介绍可用的选项,并讨论了各种要求。 这是 “开发频道”中所述的通道开发任务列表中的第一个任务。

六种消息交换模式

有三个 MEP 可供选择:

  • 数据报 (IInputChannelIOutputChannel

    当使用数据报 MEP 时,客户端使用“发后不理”交换形式发送消息。 “发后不理”交换形式是一种要求带外确认成功传递的交换形式。 消息可能会在传输过程中丢失,并且永远不会到达服务。 如果发送作在客户端成功完成,则不保证远程终结点已收到消息。 数据报是消息传递的基本构建基块,你可以基于它构建自己的协议,包括可靠的协议和安全协议。 客户端数据报通道实现 IOutputChannel 接口和服务数据报通道实现 IInputChannel 接口。

  • Request-Response (IRequestChannelIReplyChannel

    在此 MEP 中,将发送一个消息并接收一个答复。 该模式由请求-响应对组成。 请求响应调用的示例包括远程过程调用(RPC)和浏览器 GET 请求。 此模式也称为半双工。 在此 MEP 中,客户端通道实现 IRequestChannel 和服务通道实现 IReplyChannel

  • 双工 (IDuplexChannel)

    通过双工 MEP,客户端可以发送任意数目的消息,并以任意顺序接收消息。 双工 MEP 就像电话通话,所说的每一个字都是一条消息。 由于双方都可以在此 MEP 中发送和接收,因此客户端和服务通道实现的接口是 IDuplexChannel

显示三种基本消息交换模式的流程图
三种基本消息交换模式。 从上到下:数据报、请求-响应和双工。

这些 MEP 还都能支持会话。 会话(和 System.ServiceModel.Channels.ISessionChannel<TSession> 类型的 System.ServiceModel.Channels.ISession 的实现)会将通道上发送和接收的所有消息关联在一起。 请求-响应模式是独立的双消息会话,因为请求和答复是相关的。 相比之下,支持会话的请求-响应模式意味着该通道上的所有请求/响应对相互关联。 这总共提供 6 个 MEP 可供选择:

  • 数据报

  • 请求-响应

  • 双工

  • 带会话的数据报

  • 带会话的请求-响应

  • 带会话的双工

注释

对于 UDP 传输,所支持的唯一 MEP 是数据报,因为 UDP 的性质是一个“启动后不管”协议。

会话和会话通道

在网络世界中,有面向连接的协议(例如 TCP)和无连接协议(例如 UDP)。 WCF 使用术语“会话”来表示类似连接的逻辑抽象。 会话 WCF 协议类似于面向连接的网络协议,无会话 WCF 协议类似于无连接网络协议。

在通道对象模型中,每个逻辑会话都显示为会话通道的实例。 因此,客户端创建并在服务上接受的每个新会话对应于每一端的新会话通道。 下图显示了顶部的无会话通道结构,以及底部的有会话通道结构。

显示无会话通道和会话通道结构的流程图

客户端创建新的会话通道并发送消息。 在服务端,通道侦听器接收此消息并检测到它属于新会话,以便创建一个新的会话通道并将其交给应用程序(响应在通道侦听器上调用 AcceptChannel 的应用程序)。 然后,应用程序通过同一会话通道接收此消息以及在同一会话中发送的所有后续消息。

另一个客户端(或同一客户端)创建一个新的会话并发送消息。 通道侦听器检测到此消息位于新会话中,并创建新的会话通道,并重复该过程。

如果没有会话,就无法在渠道和会话之间建立关联。 因此,通道侦听器只创建一个通道,通过通道将所有接收的消息传递到应用程序。 也没有消息排序,因为没有用于维护消息顺序的会话。 上图的上半部分演示了无会话消息交换。

启动和终止会话

只需创建新的会话通道即可在客户端上启动会话。 当服务收到在新会话中发送的消息时,会话即在服务上开始。 同样,会话通过关闭或中止会话通道来终止。

这种情况的例外是 IDuplexSessionChannel,它用于以双工会话通信模式发送和接收消息。 一方可能希望停止发送消息,但继续接收消息,因此,使用 IDuplexSessionChannel 一种机制可以关闭输出会话,指示你不会再发送任何消息,但保持输入会话打开,允许你继续接收消息。

通常是在传出端关闭对话,而不在传入端关闭对话。 也就是说,可以关闭会话输出通道,从而彻底终止会话。 关闭会话输出通道会使相应的会话输入通道向在 IInputChannel.Receive 上调用 IDuplexSessionChannel 的应用程序返回空值。

然而会话输入通道不应该关闭,除非 IInputChannel.Receive 上的 IDuplexSessionChannel 返回空值,指示该会话已经关闭。 如果 IInputChannel.Receive 上的 IDuplexSessionChannel 没有返回空值,则关闭会话输入通道可能引发异常,因为当通道关闭时可能收到意外的消息。 如果接收方希望在发送方之前终止会话,则应调用 Abort 输入通道,这会突然终止会话。

编写会话通道

作为会话通道作者,有几件事是您的通道为了提供会话所必须做的。 在发送端,您的通道需要:

  • 对于每个新通道,请创建一个新会话,并将其与一个唯一字符串的新会话 ID 相关联。 或者从堆栈中位于您下方的会话通道获取一个新会话。

  • 对于使用此通道发送的每个消息,如果通道创建了会话(而不是从下面的层获取该会话),则需要将消息与会话相关联。 对于协议通道,通常通过添加 SOAP 标头来完成此作。 对于传输通道,这通常是通过创建新的传输连接或向帧协议添加会话信息来完成的。

  • 对于使用此通道发送的每个消息,需要提供上述传递保证。 如果你依赖下面的通道来提供会话,该通道还将提供传输保障。 如果你自己提供会话,则需要将这些保证作为协议的一部分实现。 一般情况下,如果要编写一个协议通道,该通道假定双方都使用 WCF,则可能需要 TCP 传输或 Reliable Messaging 通道,并依赖任一通道来提供会话。

  • 当在您的通道上调用 ICommunicationObject.Close 时,应使用指定的或默认的超时值执行必要的工作来关闭该会话。 这可以像调用你下面通道上的 Close 一样简单(如果你刚刚从它获取了会话),或者发送一条特殊的 SOAP 消息或关闭传输连接。

  • 当在您的通道上调用 Abort 时,应立即终止会话而不必执行 I/O。 这可能意味着不做任何事情,或者可能涉及中止网络连接或其他资源。

在接收端,您的通道需要:

  • 对于每个传入消息,通道侦听器必须检测它所属的会话。 如果这是会话中的第一条消息,则通道侦听器必须创建新的通道,并从调用 IChannelListener<TChannel>.AcceptChannel中返回它。 否则,通道侦听器必须找到与会话对应的现有通道,并通过该通道传递消息。

  • 如果您的通道提供会话(以及所需的传递保证),则接收方可能需要执行某些操作,例如重新排序消息或发送确认。

  • 当在您的通道上调用 Close 时,应使用指定的或默认的超时值执行必要的工作来关闭该会话。 如果通道在等待关闭超时到期时收到消息,则可能会导致异常。 这是因为通道将处于关闭状态,却突然收到消息,所以会引发异常。

  • 当在您的通道上调用 Abort 时,应立即终止会话而不必执行 I/O。 同样,这可能意味着不做任何事情,或者可能涉及中止网络连接或中止其他资源。

另请参阅