通道層概觀

通道層提供傳輸通道的抽象概念,以及通道上傳送的訊息。 它也包含 C 資料類型的串行化函式,以及從 SOAP 結構進行串行化。 通道層透過包含傳送或接收及包含主體和標頭的訊息,以及抽象訊息交換通訊協定的通道,以及提供自定義設定的屬性,來完全控制通訊

訊息

訊息是封裝網路數據的物件,特別是透過網路傳輸或接收的數據。 訊息結構是由 SOAP 所定義,其中包含一組離散標頭和訊息本文。 標頭會放在記憶體緩衝區中,並使用數據流 API 讀取或寫入訊息本文。

Diagram showing the header and body of a message.

雖然訊息的數據模型一律是 XML 數據模型,但實際的連線格式是彈性的。 在傳送訊息之前,它會使用特定的編碼方式進行編碼(例如 Text、Binary 或 MTOM)。 如需編碼的詳細資訊,請參閱 WS_ENCODING

Diagram showing several message encoding formats.

通道

通道是物件,用來在兩個或多個端點之間傳送和接收網路上的訊息。

通道具有相關聯的數據,描述如何在 傳送訊息時處理 訊息。 在通道上傳送訊息就像將它放在流失中一樣, 通道包含訊息應前往的資訊,以及如何到達該訊息。

Diagram showing channels for messages.

通道會分類為通道類型 通道類型會指定訊息可以流動的方向。 通道類型也會識別通道為會話或無會話。 會話定義為將訊息相互關聯於兩個或多個合作對象之間的抽象方式。 會話通道的範例是 TCP 通道,它會使用 TCP 連線作為具體會話實作。 無會話通道的範例是UDP,其沒有基礎會話機制。 雖然 HTTP 具有基礎 TCP 連線,但此事實不會透過此 API 直接公開,因此 HTTP 也會被視為無會話通道。

Diagram showing sessionful and sessionless channel types.

雖然通道類型描述通道的方向和會話資訊,但不會指定通道的實作方式。 通道應該使用哪些通訊協定? 通道應該如何嘗試傳遞訊息? 使用何種安全性? 是單播還是多播? 這些設定稱為通道的「系結」。 系結包含下列專案:

  • WS_CHANNEL_BINDING,識別要使用的傳輸通訊協定(TCP、UDP、HTTP、NAMEDPIPE)。
  • WS_SECURITY_DESCRIPTION,指定如何保護通道。
  • 集合 WS_CHANNEL_PROPERTY,指定其他選擇性設定。 如需屬性清單,請參閱WS_CHANNEL_PROPERTY_ID。

Diagram showing a list of channel properties.

接聽程式

若要開始通訊,用戶端會建立 Channel 物件。 但是服務如何取得其 Channel 物件? 它會藉由建立接 聽程式來進行。 建立接聽程式需要建立通道所需的相同系結資訊。 建立接聽程式之後,應用程式就可以接受來自接聽程式的通道。 由於應用程式可能會落後於接受通道,接聽程式通常會保留已準備好接受的通道佇列(最多一些配額)。

Diagram showing channels in the Listener queue.

起始通訊 (用戶端)

若要在用戶端上起始通訊,請使用下列順序。

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_CHANNEL_TYPE會指定指定通道的訊息交換模式。 支援的型別會根據系結而有所不同,如下所示:

訊息迴圈

針對每個訊息交換模式,有一個特定的「迴圈」可用來傳送或接收訊息。 迴圈描述傳送/接收多個訊息所需的作業法律順序。 迴圈如下所述為文法製作。 「結束」詞彙是傳回WS_S_END的接收,指出通道上沒有其他訊息。 平行生產指定,針對 parallel(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_CHANNEL_TYPE_DUPLEX_SESSION 的通道,在傳送之前,在伺服器上使用WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING需要成功接收。 第一次接收之後。 套用一般迴圈。

請注意,WS_CHANNEL_TYPE_REQUEST和WS_CHANNEL_TYPE_REPLY類型的通道可用來傳送和接收單向訊息(以及標準要求-回復模式)。 這是藉由關閉回復通道而不傳送回復來完成。 在此情況下,不會在要求通道上收到任何回復。 傳回值 WS_S_END 在伺服器上使用 WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING 需要成功接收,才能允許傳送,即使具有類型 為 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 作業完成。 如需詳細資訊,請參閱 WsAbortChannel 的WS_CHANNEL_STATE狀態圖表和檔

WsAbortListener API 可用來取消接聽程式的擱置 IO。 此 API 不會等候 IO 作業完成。 中止接聽程式也會導致任何擱置的接受中止。 如需詳細資訊,請參閱WS_LISTENER_STATE狀態圖表和 WsAbortListener

TCP

WS_TCP_CHANNEL_BINDING支援SOAP over TCP。 SOAP over TCP 規格是以 .NET 框架機制為基礎。

此版本不支援埠共用。 開啟的每個接聽程式都必須使用不同的埠號碼。

UDP

WS_UDP_CHANNEL_BINDING支援SOAP over UDP。

UDP 系結有一些限制:

  • 不支援安全性。
  • 訊息可能會遺失或重複。
  • 僅支援一種編碼: WS_ENCODING_XML_UTF8
  • 訊息基本上限制為 64k,而且如果大小超過網路的 MTU,通常會遺失更大的機會。

HTTP

WS_HTTP_CHANNEL_BINDING支援SOAP over HTTP。

若要控制客戶端和伺服器上的 HTTP 特定標頭,請參閱 WS_HTTP_MESSAGE_MAPPING

若要在伺服器上傳送和接收非SOAP訊息,請使用 WS_ENCODING_RAW進行WS_CHANNEL_PROPERTY_ENCODING。

NAMEDPIPES

WS_NAMEDPIPE_CHANNEL_BINDING支援SOAP over named pipes,允許使用NetNamedPipeBinding與 Windows Communication Foundation (WCF) 服務進行通訊。

相互關聯要求/回復訊息

要求/回復訊息會以下列兩種方式之一相互關聯:

  • 相互關聯是使用通道做為相互關聯機制來完成。 例如,使用WS_ADDRESSING_VERSION_TRANSPORTWS_HTTP_CHANNEL_BINDING要求訊息的回復會與要求相互關聯,因為事實是它是 HTTP 回應的實體主體。
  • 相互關聯是使用 MessageID 和 RelatesTo 標頭來完成。 這種機制與WS_ADDRESSING_VERSION_1_0WS_ADDRESSING_VERSION_0_9搭配使用(即使使用WS_HTTP_CHANNEL_BINDING也是如此)。 在此情況下,要求訊息會包含 MessageID 標頭。 回應訊息包含 RelatesTo 標頭,其值為要求的 MessageID 標頭。 RelatesTo 標頭可讓用戶端將回應與其傳送的要求相互關聯。

下列通道層 API 會根據 通道的WS_ADDRESSING_VERSION ,自動使用適當的相互關聯機制。

如果未使用這些 API,可以使用 WsSetHeader WsGetHeader 手動新增和存取標頭。

自訂通道和接聽程式

如果預先定義的通道系結集不符合應用程式的需求,則可以在建立通道或接聽程式時指定 WS_CUSTOM_CHANNEL_BINDING 來定義自定義通道和接聽程序實作。 通道/接聽程序的實際實作會透過 WS_CHANNEL_PROPERTY_CUSTOM_CHANNEL_CALLBACKSWS_LISTENER_PROPERTY_CUSTOM_LISTENER_CALLBACKS 屬性指定為一組回呼。 建立自定義通道或接聽程式之後,結果會是 可與現有 API 搭配使用的WS_CHANNELWS_LISTENER 物件。

在建立服務 Proxy 或服務主機時,也可以指定WS_CHANNEL_BINDING列舉中的WS_CUSTOM_CHANNEL_BINDING值,以及建立服務 Proxy 或服務主機時,WS_CHANNEL_PROPERTY_CUSTOM_CHANNEL_CALLBACKS和WS_LISTENER_PROPERTY_CUSTOM_LISTENER_CALLBACKS屬性,來搭配服務 Proxy 和服務主機使用自定義通道和接聽程式。

安全性

通道允許透過下列屬性限制用於各種作業層面的記憶體數量:

這些屬性具有預設值,在大部分情況下都是保守且安全的。 默認值及其任何修改都應該仔細評估,以針對可能導致遠端使用者拒絕服務的潛在攻擊向量。

通道允許透過下列屬性為作業的各個層面設定逾時值:

  • WS_CHANNEL_PROPERTY_CONNECT_TIMEOUT
  • WS_CHANNEL_PROPERTY_SEND_TIMEOUT,
  • WS_CHANNEL_PROPERTY_RECEIVE_RESPONSE_TIMEOUT,
  • WS_CHANNEL_PROPERTY_RECEIVE_TIMEOUT,
  • WS_CHANNEL_PROPERTY_RESOLVE_TIMEOUT,
  • WS_CHANNEL_PROPERTY_CLOSE_TIMEOUT。

這些屬性具有預設值,在大部分情況下都是保守且安全的。 增加逾時值會增加遠端合作物件可以保持本機資源運作的時間量,例如記憶體、套接字和線程執行同步 I/O。 應用程式應該評估預設值,並在增加逾時時小心,因為它可能會開啟可能造成遠端電腦拒絕服務的潛在攻擊媒介。

使用WSAPI通道 API時,應該仔細評估的一些其他組態選項和應用程式設計考慮:

  • 使用通道/接聽程式層時,由應用程式在伺服器端建立及接受通道。 同樣地,應用程式可以建立和開啟用戶端上的通道。 應用程式應該在這些作業上放置上限,因為每個通道都會耗用記憶體和其他有限的資源,例如套接字。 當建立通道以回應遠端合作物件所觸發的任何動作時,應用程式應該特別小心。
  • 應用程式必須撰寫邏輯來建立通道並接受它們。 每個通道都會耗用有限的資源,例如記憶體和套接字。 應用程式應該在願意接受的通道數目上具有上限,或遠端合作物件可能會進行許多連線,導致 OOM,因而拒絕服務。 它也應該使用小型逾時主動接收來自這些連線的訊息。 如果未收到任何訊息,則作業將會逾時,且應該釋放連線。
  • 應用程式可以藉由解譯 ReplyTo 或 FaultTo SOAP 標頭來傳送回復或錯誤。 安全的做法是只接受「匿名」的 ReplyTo 或 FaultTo 標頭,這表示應該使用現有的連線 (TCP、HTTP) 或來源 IP (UDP) 來傳送 SOAP 回復。 應用程式在建立資源(例如通道)以回復不同的位址時,應特別小心,除非訊息是由可以代表回復所傳送位址的一方所簽署。
  • 通道層中完成的驗證無法取代透過安全性達成的數據完整性。 應用程式必須依賴 WWSAPI 的安全性功能,以確保它與受信任的實體通訊,而且也必須依賴安全性來確保數據完整性。

同樣地,使用 WWSAPI 訊息 API 時,應該仔細評估訊息組態選項和應用程式設計考慮:

  • 您可以使用 WS_MESSAGE_PROPERTY_HEAP_PROPERTIES 屬性來設定用來儲存訊息標頭的堆積大小。 增加此值可讓訊息的標頭耗用更多記憶體,這可能會導致 OOM。
  • 訊息對象的使用者必須瞭解標頭存取 API 與訊息中的標頭數目有關,因為它們會檢查重複專案。 需要訊息中許多標頭的設計可能會導致過多的CPU使用量。
  • 訊息中的標頭數目上限可以使用 WS_MESSAGE_PROPERTY_MAX_PROCESSED_HEADERS 屬性來設定。 此外,也會根據訊息堆積的大小來隱含限制。 增加這兩個值可讓更多標頭存在,這會增加尋找標頭所需的時間(使用標頭存取 API 時)。