通道層概觀

通道層提供傳輸通道的抽象概念,以及通道上傳送的訊息。 它也包含將 C 資料類型序列化至 SOAP 結構及從 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.

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

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的接收, (請參閱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_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 作業 () 完成。 如需詳細資訊,請參閱WsAbortChannelWS_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,允許使用 NetNamedPipeBinding 與 Windows Communication Foundation (WCF) 服務進行通訊。

使要求/回復訊息相互關聯

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

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

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

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

自訂通道和接聽程式

如果預先定義的通道系結集不符合應用程式的需求,則可以藉由在建立通道或接聽程式時指定 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_CALLBACKSWS_LISTENER_PROPERTY_CUSTOM_LISTENER_CALLBACKS屬性來搭配使用。

安全性

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

這些屬性的預設值對於大部分案例而言都是保守且安全的。 預設值和對它們所做的任何修改都應該仔細評估,以針對可能導致遠端使用者拒絕服務的潛在攻擊向量。

通道允許透過屬性為各種作業層面設定逾時值,例如:

這些屬性的預設值對於大部分案例而言都是保守且安全的。 增加逾時值會增加遠端合作物件可以保持本機資源運作的時間量,例如記憶體、通訊端和執行同步 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) ,與訊息中的標頭數目有關,因為它們會檢查重複專案。 需要訊息中許多標頭的設計可能會導致 CPU 使用量過多。
  • 訊息中的標頭數目上限可以使用 WS_MESSAGE_PROPERTY_MAX_PROCESSED_HEADERS 屬性來設定。 根據訊息的堆積大小,也有隱含的限制。 增加這兩個值可讓更多標頭存在,這會在使用標頭存取 API) 時,複合尋找標頭 (所需的時間。