通道层概述

通道层提供传输通道的抽象,以及通道上发送的消息。 它还包括用于将 C 数据类型序列化到 SOAP 结构以及从 SOAP 结构序列化的函数。 通道层通过消息(包括发送或接收的数据以及包含正文和标头)以及提取消息交换协议并提供属性以自定义设置的通道,实现对通信的完全控制。

消息

消息是封装网络数据的对象, 具体而言,是网络上传输或接收的数据。 消息结构由 SOAP 定义,具有一组离散的标头和消息正文。 标头放置在内存缓冲区中,消息正文是使用流 API 读取或写入的。

显示消息标头和正文的关系图。

尽管消息的数据模型始终是 XML 数据模型,但实际的线路格式是灵活的。 在传输消息之前,会使用特定的编码方法(例如文本、二进制或 MTOM ()对消息) 。 有关 编码 _ 详细信息 ,请参阅 WS ENCODING。

显示多种消息编码格式的关系图。

通道

通道是一个对象,用于在两个或多个终结点之间的网络上发送和接收消息。

通道具有关联数据,用于描述在发送消息时如何处理消息。 在通道上发送消息就像将其置于一个槽中一样 - 该通道包含消息应发送到何处以及如何获取消息的信息。

显示消息通道的关系图。

通道分为通道 类型。 通道类型指定消息可以流动的方向。 通道类型还标识通道是会话的还是无会话的。 会话定义为在两个或多个参与方之间关联消息的抽象方式。 会话通道的一个示例是 TCP 通道,该通道使用 TCP 连接作为具体的会话实现。 无会话通道的一个示例是 UDP,该通道没有基础会话机制。 尽管 HTTP 具有基础 TCP 连接,但此事实不会通过此 API 直接公开,因此 HTTP 也被视为无会话通道。

显示会话和无会话通道类型的关系图。

尽管通道类型描述通道的方向和会话信息,但未指定通道的实现方式。 通道应该使用什么协议? 通道应尝试传递消息有多困难? 使用了哪种安全? 它是单播还是多播? 这些设置称为通道的"绑定"。 绑定由以下内容组成:

显示通道属性列表的关系图。

侦听器

为了开始通信,客户端将创建 Channel 对象。 但是,服务如何获取其 Channel 对象? 它通过创建侦听器 来这样做。 创建侦听器需要创建通道所需的相同绑定信息。 创建侦听器后,应用程序可以从侦听器接受通道。 由于应用程序可能在接受通道方面滞后,因此侦听器通常会保留一个通道队列,这些通道已准备好接受 (一些配额) 。

显示侦听器队列中通道的关系图。

启动客户端 (通信)

若要在客户端上启动通信,请使用以下序列。

WsCreateChannel
for each address being sent to
{
    WsOpenChannel           // open channel to address
    // send and/or receive messages
    WsCloseChannel          // close channel
    WsResetChannel?         // reset if opening again
}
WsFreeChannel

接受服务器 (通信)

若要在服务器上接受传入通信,请使用以下序列。

WsCreateListener
WsOpenListener
for each channel being accepted (can be done in parallel)
{
    WsCreateChannelForListener
    for each accept
    {
        WsAcceptChannel     // accept the channel
        // send and/or receive messages
        WsCloseChannel      // close the channel
        WsResetChannel?     // reset if accepting again
    }
    WsFreeChannel
}
WsCloseListener
WsFreeListener

向客户端 (服务器发送消息)

若要发送消息,请使用以下序列。

WsCreateMessageForChannel
for each message being sent
{
    WsSendMessage       // send message
    WsResetMessage?     // reset if sending another message
}
WsFreeMessage

WsSendMessage函数不允许流式处理,并假定正文仅包含一个元素。 若要避免这些约束,请使用以下序列,而不是 WsSendMessage

WsInitializeMessage     // initialize message to WS_BLANK_MESSAGE
WsSetHeader             // serialize action header into header buffer
WsAddressMessage?       // optionally address message
for each application defined header
{
    WsAddCustomHeader   // serialize application-defined headers into header buffer
}
WsWriteMessageStart     // write out the headers of the message
for each element of the body
{
    WsWriteBody         // serialize the element of the body
    WsFlushBody?        // optionally flush the body
}
WsWriteMessageEnd       // write the end of the message

WsWriteBody函数使用序列化来写入正文元素。 若要将数据直接写入 XML 编写器,请使用以下序列,而不是 WsWriteBody

WS_MESSAGE_PROPERTY_BODY_WRITER     // get the writer used to write the body
WsWriteStartElement
// use the writer functions to write the body
WsWriteEndElement
// optionally flush the body
WsFlushBody?        

WsAddCustomHeader函数使用序列化将标头设置为消息的标头缓冲区。 若要使用 XML 编写器编写标头,请使用以下序列而不是 WsAddCustomHeader

WS_MESSAGE_PROPERTY_HEADER_BUFFER   // get the header buffer 
WsCreateWriter                      // create an xml writer
WsSetOutputToBuffer                 // specify output of writer should go to buffer
WsMoveWriter*                       // move to inside envelope header element
WsWriteStartElement                 // write application header start element
// use the writer functions to write the header 
WsWriteEndElement                   // write appilcation header end element

接收客户端 (服务器消息)

若要接收消息,请使用以下序列。

WsCreateMessageForChannel
for each message being received
{
    WsReceiveMessage            // receive a message
    WsGetHeader*                // optionally access standard headers such as To or Action
    WsResetMessage              // reset if reading another message
}
WsFreeMessage

WsReceiveMessage函数不允许流式处理,并且假定正文仅包含一个元素,并且消息 (操作的类型和正文) 架构是已知的。 若要避免这些约束,请使用以下序列而不是 WsReceiveMessage

WsReadMessageStart              // read all headers into header buffer
for each standard header
{
    WsGetHeader                 // deserialize standard header such as To or Action
}
for each application defined header
{
    WsGetCustomHeader           // deserialize application defined header
}
for each element of the body
{
    WsFillBody?                 // optionally fill the body
    WsReadBody                  // deserialize element of body
}
WsReadMessageEnd                // read end of message

WsReadBody函数使用序列化来读取正文元素。 若要直接从 XML 读取器 读取数据,请使用以下序列,而不是 WsReadBody

WS_MESSAGE_PROPERTY_BODY_READER     // get the reader used to read the body
WsFillBody?                         // optionally fill the body
WsReadToStartElement                // read up to the body element
WsReadStartElement                  // consume the start of the body element
// use the read functions to read the contents of the body element
WsReadEndElement                    // consume the end of the body element

WsGetCustomHeader函数使用序列化从消息的标头缓冲区获取标头。 若要使用 XML 读取器 读取标头,请使用以下序列,而不是 WsGetCustomHeader

WS_MESSAGE_PROPERTY_HEADER_BUFFER   // get the header buffer 
WsCreateReader                      // create an xml reader
WsSetInputToBuffer                  // specify input of reader should be buffer
WsMoveReader*                       // move to inside header element
while looking for header to read
{
    WsReadToStartElement            // see if the header matches the application header
    if header matched
    {
        WsGetHeaderAttributes?      // get the standard header attributes
        WsReadStartElement          // consume the start of the header element
        // use the read functions to read the contents of the header element
        WsReadEndElement            // consume the end of the header element
    }
    else
    {
        WsSkipNode                  // skip the header element
    }
}                

请求回复 (客户端)

可以按照以下顺序在客户端上执行请求-答复。

WsCreateMessageForChannel               // create request message 
WsCreateMessageForChannel               // create reply message 
for each request reply
{
    WsRequestReply                      // send request, receive reply
    WsResetMessage?                     // reset request message (if repeating)
    WsResetMessage?                     // reset reply message (if repeating)
}
WsFreeMessage                           // free request message
WsFreeMessage                           // free reply message

WsRequestReply函数为请求和答复消息的正文假定单个元素,并且消息 (操作的类型和正文) 是已知的。 若要避免这些限制,可以手动发送请求和答复消息,如以下序列所示。 此序列与用于发送和接收消息的前面序列匹配,但已说明的除外。

WsInitializeMessage     // initialize message to WS_BLANK_MESSAGE
WsSetHeader             // serialize action header into header buffer
WsAddressMessage?       // optionally address message

// the following block is specific to sending a request
{
    generate a unique MessageID for request
    WsSetHeader         // set the message ID            
}

for each application defined header
{
    WsAddCustomHeader   // serialize application-defined headers into header buffer
}
WsWriteMessageStart     // write out the headers of the message
for each element of the body
{
    WsWriteBody         // serialize the element of the body
    WsFlushBody?        // optionally flush the body
}
WsWriteMessageEnd       // write the end of the message

WsReadMessageStart      // read all headers into header buffer

// the following is specific to receiving a reply
{
    WsGetHeader         // deserialize RelatesTo ID of reply
    verify request MessageID is equal to RelatesTo ID
}

for each standard header
{
    WsGetHeader         // deserialize standard header such as To or Action
}
for each application defined header
{
    WsGetCustomHeader   // deserialize application defined header
}
for each element of the body
{
    WsFillBody?         // optionally fill the body
    WsReadBody          // deserialize element of body
}
WsReadMessageEnd        // read end of message                

请求回复 (服务器)

若要在服务器上接收请求消息,请使用上一部分有关接收消息的相同顺序。

若要发送答复或错误消息,请使用以下序列。

WsCreateMessageForChannel
for each reply being sent
{
    WsSendReplyMessage | WsSendFaultMessageForError  // send reply or fault message
    WsResetMessage?     // reset if sending another message
}
WsFreeMessage

WsSendReplyMessage函数假定正文中具有单个元素,不允许流式处理。 若要避免这些限制,请使用以下序列。 这与用于发送消息的前面序列相同,但在初始化时使用 WS _ REPLY _ MESSAGE而不是 WS BLANK _ _ MESSAGE。

// the following block is specific to sending a reply
{
    WsInitializeMessage // initialize message to WS_REPLY_MESSAGE
}
WsSetHeader             // serialize action header into header buffer                                
WsAddressMessage?       // optionally address message
for each application defined header
{
    WsAddCustomHeader   // serialize application-defined headers into header buffer
}
WsWriteMessageStart     // write out the headers of the message
for each element of the body
{
    WsWriteBody         // serialize the element of the body
    WsFlushBody?        // optionally flush the body
}
WsWriteMessageEnd       // write the end of the message

消息交换模式

WS _ 通道 _ 类型指示给定通道可能的消息交换模式。 支持的类型因绑定而异,如下所示:

消息循环

对于每个消息交换模式,都有一个可用于发送或接收消息的特定"循环"。 循环描述发送/接收多条消息所需的操作合法顺序。 下面将循环描述为语法生产。 "end"一词是返回 WS _ S _ END 的接收 (请参阅 Windows Web 服务返回值) ,指示通道上不再有消息可用。 并行生产指定对于并行 (x & y) 操作 x 可以与 y 同时完成。

客户端上使用了以下循环:

client-loop := client-request-loop | client-duplex-session-loop | client-duplex-loop
client-request-loop := open (send (receive | end))* close // WS_CHANNEL_TYPE_REQUEST
client-duplex-session-loop := open parallel(send* & receive*) parallel(send? & end*) close // WS_CHANNEL_TYPE_DUPLEX_SESSION
client-duplex-loop := open parallel(send & receive)* close // WS_CHANNEL_TYPE_DUPLEX

在服务器上使用以下循环:

server-loop: server-reply-loop | server-duplex-session-loop | server-duplex-loop
server-reply-loop := accept receive end* send? end* close // WS_CHANNEL_TYPE_REPLY
server-duplex-session-loop := accept parallel(send* & receive*) parallel(send* & end*) close // WS_CHANNEL_TYPE_DUPLEX_SESSION
server-input-loop := accept receive end* close // WS_CHANNEL_TYPE_INPUT

在服务器上使用 WS _ SECURITY CONTEXT MESSAGE _ SECURITY _ _ _ BINDING 需要成功接收,然后才能发送,即使具有类型为 WS CHANNEL TYPE DUPLEX _ _ SESSION _ _ 的通道。 第一次接收之后。 应用常规循环。

请注意 ,WS _ CHANNEL TYPE _ _ REQUESTWS CHANNEL TYPE _ _ _ REPLY 类型的通道可用于发送和接收单向消息 (以及标准请求-答复模式) 。 这是通过关闭回复通道而不发送答复完成的。 在这种情况下,请求通道上不会收到回复。 在服务器上使用 WS _ SECURITY CONTEXT MESSAGE _ SECURITY _ _ _ BINDING的返回值 WS _ S _ END 需要先成功接收,然后才能允许发送,即使通道的类型为 WS CHANNEL TYPE DUPLEX _ _ _ _ SESSION。 第一次接收后,将应用常规循环。

将返回 ,指示没有可用的消息。

客户端或服务器循环可能通过使用多个通道实例并行执行。

parallel-client: parallel(client-loop(channel1) & client-loop(channel2) & ...)
parallel-server: parallel(server-loop(channel1) & server-loop(channel2) & ...)

消息筛选

服务器通道可以筛选收到的不适合应用程序的消息,例如建立安全上下文 的消息。 在这种情况下 ,WS _ S _ END 会从 WsReadMessageStart 返回,并且该通道上没有可用的应用程序消息。 但是,这并不意味着客户端打算结束与服务器的通信。 其他通道上可能会提供更多消息。 请参阅 WsShutdownSessionChannel

取消

WsAbortChannel函数用于取消通道的挂起 IO。 此 API 不会等待 IO 操作 () 完成。 有关详细信息, 请参阅 WS _ CHANNEL _ STATE 状态图和 WsAbortChannel 文档。

WsAbortListener API 用于取消侦听器的挂起 IO。 此 API 不会等待 IO 操作 () 完成。 中止侦听器也会导致任何挂起的接受被中止。 有关详细信息,请参阅 WS _ LISTENER _ STATE状态图和 WsAbortListener。

TCP

WS _ TCP _ 通道 _ 绑定支持基于 TCP 的 SOAP。 基于 TCP 的 SOAP 规范基于 .NET 帧机制。

此版本不支持端口共享。 打开的每个侦听器都必须使用不同的端口号。

UDP

WS _ UDP _ 通道 _ 绑定支持通过 UDP 进行 SOAP。

UDP 绑定存在许多限制:

  • 不支持安全性。
  • 消息可能会丢失或重复。
  • 仅支持一种编码 :WS _ ENCODING XML _ _ UTF8
  • 消息基本上限制为 64k,如果大小超过网络的 MTU,则通常丢失的可能性更大。

HTTP

WS _ HTTP _ 通道 _ 绑定支持通过 HTTP 的 SOAP。

若要控制客户端和服务器上特定于 HTTP 的标头,请参阅 WS _ HTTP _ 消息 _ 映射

若要在服务器上发送和接收非 SOAP 消息,请将 WS _ ENCODING _ RAW 用于 WS _ CHANNEL PROPERTY _ _ ENCODING

NAMEDPIPES

WS _ NAMEDPIPE _ 通道 _ 绑定支持通过命名管道使用 SOAP,允许使用 NetNamedPipeBinding 与 Windows Communication Foundation (WCF) 服务通信。

关联请求/答复消息

请求/答复消息的关联方式有两种:

  • 关联是使用通道作为关联机制完成。 例如,使用 WS _ 寻址 _ 版本 _ 传输WS _ HTTP _ 通道 _ 绑定时,请求消息的回复与请求相关联,即它是 HTTP 响应的实体正文。
  • 使用 MessageID 和 RelatesTo 标头完成关联。 此机制用于 WS _ 寻址版本 _ _ 1 _ 0WS 寻址版本 _ _ _ _ 0 9 (即使使用 WS HTTP 通道 _ _ _ 绑定) 。 在这种情况下,请求消息包含 MessageID 标头。 响应消息包括一个 RelatesTo 标头,该标头具有请求的 MessageID 标头的值。 RelatesTo 标头允许客户端将响应与其发送的请求相关联。

以下通道层 API 根据通道的 WS 寻址版本自动使用 _ _ 适当的 关联机制。

如果未使用这些 API,可以使用 WsSetHeader 或 WsGetHeader 手动添加和 访问标头

自定义通道和侦听器

如果预定义的通道绑定集不满足应用程序的需求,则可以通过在创建通道或侦听器时指定 WS _ 自定义通道 _ _ 绑定 来定义自定义通道和侦听器实现。 通道/侦听器的实际实现通过 WS 通道属性自定义通道回调或 WS _ _ _ _ _ 侦听器属性自定义侦听器回调属性指定为一 _ _ _ _ _ 组 回调。 创建自定义通道或侦听器后,结果为 WS _ CHANNELWS _ LISTENER 对象,该对象可用于现有 API。

通过在创建服务代理或服务主机时指定 WS 通道绑定枚举中的 WS _ 自定义 _ _ 通道 _ _ 绑定值以及 WS _ 通道 _ _ _ _ 属性自定义通道回调和 WS 侦听器属性 _ _ _ 自定义 _ _侦听器回调属性,还可以将自定义通道和侦听器与服务代理和服务主机一起使用。

安全

通道允许通过以下属性限制用于操作的各个方面的内存量:

这些属性具有默认值,这些值对于大多数方案都是保守且安全的。 应针对可能导致远程用户拒绝服务的潜在攻击途径仔细评估默认值及其所做的任何修改。

通道允许通过属性为操作的各个方面设置超时值,例如:

这些属性具有默认值,这些值对于大多数方案都是保守且安全的。 增加超时值会增加远程方可以保持本地资源活动状态的时间,例如内存、套接字和执行同步 I/O 的线程。 应用程序应评估默认值,在增加超时值时请谨慎,因为它可能会打开可能导致远程计算机拒绝服务的潜在攻击途径。

使用 WWSAPI 通道 API 时,应仔细评估一些其他配置选项和应用程序设计注意事项:

  • 使用通道/侦听器层时,由应用程序在服务器端创建和接受通道。 同样,由应用程序在客户端创建和打开通道。 应用程序应在这些操作上设置上限,因为每个通道都消耗内存和其他有限的资源(如套接字)。 创建通道以响应远程方触发的任何操作时,应用程序应特别小心。
  • 由应用程序编写逻辑来创建和接受通道。 每个通道使用有限的资源,例如内存和套接字。 应用程序应具有它愿意接受的通道数上限,或者远程方可以建立许多连接,从而导致 OOM 并因此拒绝服务。 它还应使用较小的超时主动接收来自这些连接的消息。 如果未收到任何消息,则操作将退出,应释放连接。
  • 由应用程序通过解释 ReplyTo 或 FaultTo SOAP 标头发送答复或错误。 安全做法是仅采用"匿名"的 ReplyTo 或 FaultTo 标头,这意味着现有的连接 (TCP、HTTP) 或源 IP (UDP) 应该用于发送 SOAP 答复。 应用程序在创建资源 ((如通道) )时应非常小心,以便回复其他地址,除非消息由可以代表答复发送到的地址的一方签名。
  • 通道层中所做的验证不能替代通过安全性实现的数据完整性。 应用程序必须依赖 WWSAPI 的安全功能来确保它正在与受信任的实体通信,并且还必须依赖安全性来确保数据完整性。

同样,使用 WWSAPI 消息 API 时,应仔细评估消息配置选项和应用程序设计注意事项:

  • 可以使用 WS _ MESSAGE PROPERTY HEAP _ _ _ PROPERTIES 属性配置用于存储消息标头的堆的大小。 增大此值可使消息的标头占用更多内存,这可能会导致 OOM。
  • 消息对象的用户必须意识到,与消息中的标头数 (API 是 O (n n) ,因为它们会检查重复项。 在消息中需要多个标头的设计可能会导致 CPU 使用率过高。
  • 可以使用 WS MESSAGE PROPERTY MAX PROCESSED HEADERS 属性配置消息 _ _ _ 中 _ 的最大 _ 标头 数。 还有一个基于消息堆大小的隐式限制。 增加这两个值可允许存在更多的标头,这增加了在使用标头访问 API 时查找标头 (所需的) 。