Visão geral da Camada de Canal

A Camada de Canal fornece uma abstração do canal de transporte, bem como mensagens enviadas no canal. Ele também inclui funções para a serialização de tipos de dados C de e para estruturas SOAP. A Camada de Canal permite o controle total das comunicações por meio de mensagens que consistem em dados enviados ou recebidos e contendo corpos e cabeçalhos, além de canais que trocam protocolos abstratos de troca de mensagens e fornecem propriedades para personalizar configurações.

Mensagem

Uma mensagem é um objeto que encapsula dados de rede – especificamente, dados transmitidos ou recebidos por uma rede. A estrutura da mensagem é definida por SOAP, com um conjunto discreto de cabeçalhos e um corpo da mensagem. Os cabeçalhos são colocados em um buffer de memória e o corpo da mensagem é lido ou gravado usando uma API de fluxo.

Diagram showing the header and body of a message.

Embora o modelo de dados de uma mensagem seja sempre o modelo de dados XML, o formato de fio real é flexível. Antes de uma mensagem ser transmitida, ela é codificada usando uma codificação específica (como Texto, Binário ou MTOM). Consulte WS_ENCODING para obter mais informações sobre codificações.

Diagram showing several message encoding formats.

Canal

Um canal é um objeto usado para enviar e receber mensagens em uma rede entre dois ou mais pontos de extremidade.

Os canais têm dados associados que descrevem como abordar a mensagem quando ela é enviada. Enviar uma mensagem em um canal é como colocá-la em um paraquedas – o canal inclui as informações para onde a mensagem deve ir e como obtê-la lá.

Diagram showing channels for messages.

Os canais são categorizados em tipos de canal. Um tipo de canal especifica qual direção as mensagens podem fluir. O tipo de canal também identifica se o canal é sessão ou sem sessão. Uma sessão é definida como uma forma abstrata de correlacionar mensagens entre duas ou mais partes. Um exemplo de um canal com sessão é um canal TCP, que usa a conexão TCP como a implementação de sessão concreta. Um exemplo de um canal sem sessão é UDP, que não tem um mecanismo de sessão subjacente. Embora HTTP tenha conexões TCP subjacentes, esse fato não é exposto diretamente por meio dessa API e, portanto, HTTP também é considerado um canal sem sessão.

Diagram showing sessionful and sessionless channel types.

Embora os tipos de canal descrevam as informações de direção e sessão de um canal, eles não especificam como o canal é implementado. Qual protocolo o canal deve usar? Quão difícil o canal deve tentar entregar a mensagem? Que tipo de segurança é usada? É singlecast ou multicast? Essas configurações são conhecidas como a "associação" do canal. A associação consiste no seguinte:

Diagram showing a list of channel properties.

Ouvinte

Para começar a se comunicar, o cliente cria um objeto Channel. Mas como o serviço obtém seu objeto Channel? Ele faz isso criando um Ouvinte. Criar um ouvinte requer as mesmas informações de associação necessárias para criar um canal. Depois que um ouvinte for criado, o aplicativo poderá aceitar canais do Ouvinte. Como o aplicativo pode ficar para trás na aceitação de canais, os ouvintes normalmente mantêm uma fila de canais que estão prontos para aceitar (até alguma cota).

Diagram showing channels in the Listener queue.

Iniciando comunicação (cliente)

Para iniciar a comunicação no cliente, use a sequência a seguir.

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

Aceitando comunicação (servidor)

Para aceitar comunicações de entrada no servidor, use a sequência a seguir.

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

Enviando mensagens (cliente ou servidor)

Para enviar mensagens, use a sequência a seguir.

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

A função WsSendMessage não permite streaming e pressupõe que o corpo contenha apenas um elemento. Para evitar essas restrições, use a sequência a seguir em vez de 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

A função WsWriteBody usa serialização para gravar os elementos do corpo. Para gravar os dados diretamente no Gravador XML, use a sequência a seguir em vez de 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?        

A função WsAddCustomHeader usa serialização para definir os cabeçalhos para o buffer de cabeçalho da mensagem. Para usar o Gravador XML para escrever um cabeçalho, use a sequência a seguir em vez de 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

Recebendo mensagens (cliente ou servidor)

Para receber mensagens, use a sequência a seguir.

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

A função WsReceiveMessage não permite streaming e pressupõe que o corpo contém apenas um elemento e que o tipo da mensagem (ação e esquema do corpo) é conhecido antecipadamente. Para evitar essas restrições, use a sequência a seguir em vez de 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

A função WsReadBody usa serialização para ler os elementos do corpo. Para ler os dados diretamente do Leitor de XML, use a sequência a seguir em vez de 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

As funções WsGetCustomHeader usam serialização para obter os cabeçalhos do buffer de cabeçalho da mensagem. Para usar o Leitor XML para ler um cabeçalho, use a sequência a seguir em vez de 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
    }
}                

Resposta de solicitação (cliente)

A execução de uma solicitação-resposta no cliente pode ser feita com a sequência a seguir.

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

A função WsRequestReply pressupõe um único elemento para o corpo das mensagens de solicitação e resposta e que o tipo da mensagem (ação e esquema do corpo) são conhecidos antecipadamente. Para evitar essas limitações, a solicitação e a mensagem de resposta podem ser enviadas manualmente, conforme mostrado na sequência a seguir. Essa sequência corresponde à sequência anterior para enviar e receber uma mensagem, exceto quando anotada.

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                

Resposta à Solicitação (servidor)

Para receber uma mensagem de solicitação no servidor, use a mesma sequência descrita na seção anterior ao receber mensagens.

Para enviar uma mensagem de resposta ou falha, use a sequência a seguir.

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

A função WsSendReplyMessage pressupõe um único elemento no corpo e não permite streaming. Para evitar essas limitações, use a sequência a seguir. Isso é o mesmo que a sequência anterior para enviar uma mensagem, mas usa WS_REPLY_MESSAGE em vez de WS_BLANK_MESSAGE ao inicializar.

// 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

Padrões de Exchange de mensagem

O WS_CHANNEL_TYPE dita o padrão de troca de mensagens possível para um determinado canal. O tipo com suporte varia de acordo com a associação, da seguinte maneira:

Loops de Mensagem

Para cada padrão de troca de mensagens, há um "loop" específico que pode ser usado para enviar ou receber mensagens. O loop descreve a ordem legal das operações necessárias para enviar/receber várias mensagens. Os loops são descritos abaixo como produções gramaticais. O termo 'end' é um recebimento em que WS_S_END é retornado (Consulte Windows Valores retornados dos Serviços Web), indicando que não há mais mensagens disponíveis no canal. A produção paralela especifica que para parallel(x & y) essa operação x pode ser feita simultaneamente com y.

Os loops a seguir são usados no cliente:

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

Os loops a seguir são usados no servidor:

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

Usar o WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING no servidor requer um recebimento bem-sucedido antes que o envio seja permitido mesmo com um canal do tipo WS_CHANNEL_TYPE_DUPLEX_SESSION. Após o primeiro recebimento. o loop regular se aplica.

Observe que canais do tipo WS_CHANNEL_TYPE_REQUEST e WS_CHANNEL_TYPE_REPLY podem ser usados para enviar e receber mensagens unidirecionais (bem como o padrão de solicitação-resposta padrão). Isso é feito fechando o canal de resposta sem enviar uma resposta. Nesse caso, não haverá resposta recebida no canal de solicitação. O valor retornado WS_S_END Usar o WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING no servidor requer um recebimento bem-sucedido antes que o envio seja permitido mesmo com um canal do tipo WS_CHANNEL_TYPE_DUPLEX_SESSION. Depois que o primeiro receber, o loop regular será aplicado.

será retornado, indicando nenhuma mensagem disponível.

Os loops de cliente ou servidor podem ser feitos em paralelo entre si usando várias instâncias de canal.

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

Filtragem de Mensagem

Um canal de servidor pode filtrar mensagens recebidas que não se destinam ao aplicativo, como mensagens que estabelecem um contexto de segurança. Nesse caso , WS_S_END será retornado do WsReadMessageStart e nenhuma mensagem de aplicativo estará disponível nesse canal. No entanto, isso não sinaliza que o cliente pretendia encerrar a comunicação com o servidor. Mais mensagens podem estar disponíveis em outro canal. Consulte WsShutdownSessionChannel.

Cancelamento

A função WsAbortChannel é usada para cancelar a E/S pendente para um canal. Essa API não aguardará a conclusão das operações de E/S. Consulte o diagrama de estado WS_CHANNEL_STATE e a documentação do WsAbortChannel para obter mais informações.

A API WsAbortListener é usada para cancelar a E/S pendente para um ouvinte. Essa API não aguardará a conclusão das operações de E/S. Anular um ouvinte fará com que qualquer aceitação pendente também seja anulada. Consulte o diagrama de estado WS_LISTENER_STATE e WsAbortListener para obter mais informações.

TCP

O WS_TCP_CHANNEL_BINDING dá suporte a SOAP via TCP. A especificação SOAP over TCP baseia-se no mecanismo de enquadramento do .NET.

Não há suporte para o compartilhamento de portas nesta versão. Cada ouvinte aberto deve usar um número de porta diferente.

UDP

O WS_UDP_CHANNEL_BINDING dá suporte a SOAP por UDP.

Há várias limitações com a associação UDP:

  • Não há suporte para segurança.
  • As mensagens podem ser perdidas ou duplicadas.
  • Há suporte para apenas uma codificação: WS_ENCODING_XML_UTF8.
  • As mensagens são fundamentalmente limitadas a 64k e, frequentemente, têm maior chance de serem perdidas se o tamanho exceder a MTU da rede.

HTTP

O WS_HTTP_CHANNEL_BINDING dá suporte a SOAP via HTTP.

Para controlar cabeçalhos específicos de HTTP no cliente e no servidor, consulte WS_HTTP_MESSAGE_MAPPING.

Para enviar e receber mensagens não SOAP no servidor, use WS_ENCODING_RAW para WS_CHANNEL_PROPERTY_ENCODING.

NAMEDPIPES

O WS_NAMEDPIPE_CHANNEL_BINDING dá suporte a SOAP em pipes nomeados, permitindo comunicação com o serviço WCF (Windows Communication Foundation) usando NetNamedPipeBinding.

Correlacionando mensagens de solicitação/resposta

As mensagens de solicitação/resposta são correlacionadas de duas maneiras:

  • A correlação é feita usando o canal como o mecanismo de correlação. Por exemplo, ao usar WS_ADDRESSING_VERSION_TRANSPORT e WS_HTTP_CHANNEL_BINDING a resposta para a mensagem de solicitação é correlacionada à solicitação pelo fato de ser o corpo da entidade da resposta HTTP.
  • A correlação é feita usando os cabeçalhos MessageID e RelatesTo. Esse mecanismo é usado com WS_ADDRESSING_VERSION_1_0 e WS_ADDRESSING_VERSION_0_9 (mesmo ao usar WS_HTTP_CHANNEL_BINDING). Nesse caso, a mensagem de solicitação inclui o cabeçalho MessageID. A mensagem de resposta inclui um cabeçalho RelatesTo que tem o valor do cabeçalho MessageID da solicitação. O cabeçalho RelatesTo permite que o cliente correlacionar uma resposta com uma solicitação enviada.

As APIs da camada de canal a seguir usam automaticamente os mecanismos de correlação apropriados com base no WS_ADDRESSING_VERSION do canal.

Se essas APIs não forem usadas, os cabeçalhos poderão ser adicionados e acessados manualmente usando WsSetHeader ou WsGetHeader.

Canais personalizados e ouvintes

Se o conjunto predefinido de associações de canal não atender às necessidades do aplicativo, uma implementação personalizada de canal e ouvinte poderá ser definida especificando WS_CUSTOM_CHANNEL_BINDING ao criar o canal ou o ouvinte. A implementação real do canal/ouvinte é especificada como um conjunto de retornos de chamada por meio de propriedades WS_CHANNEL_PROPERTY_CUSTOM_CHANNEL_CALLBACKS ou WS_LISTENER_PROPERTY_CUSTOM_LISTENER_CALLBACKS . Depois que um canal personalizado ou ouvinte é criado, o resultado é um objeto WS_CHANNEL ou WS_LISTENER que pode ser usado com APIs existentes.

Um canal personalizado e um ouvinte também podem ser usados com o Proxy de Serviço e o Host de Serviço especificando o valor WS_CUSTOM_CHANNEL_BINDING na enumeração WS_CHANNEL_BINDING e as propriedades WS_CHANNEL_PROPERTY_CUSTOM_CHANNEL_CALLBACKS e WS_LISTENER_PROPERTY_CUSTOM_LISTENER_CALLBACKS ao criar o Proxy de Serviço ou o Host de Serviço.

Segurança

O canal permite limitar a quantidade de memória usada para vários aspectos das operações por meio de propriedades como:

Essas propriedades têm valores padrão que são conservadores e seguros para a maioria dos cenários. Os valores padrão e quaisquer modificações neles devem ser cuidadosamente avaliados em relação a possíveis vetores de ataque, o que pode causar negação de serviço por um usuário remoto.

O canal permite definir valores de tempo limite para vários aspectos das operações por meio de propriedades como:

Essas propriedades têm valores padrão que são conservadores e seguros para a maioria dos cenários. Aumentar os valores de tempo limite aumenta a quantidade de tempo que uma parte remota pode manter um recurso local ativo, como memória, soquetes e threads fazendo E/S síncrona. Um aplicativo deve avaliar os valores padrão e ter cuidado ao aumentar um tempo limite, pois pode abrir possíveis vetores de ataque, o que pode causar negação de serviço de um computador remoto.

Algumas das outras opções de configuração e considerações de design de aplicativo que devem ser avaliadas cuidadosamente ao usar a API de Canal do WWSAPI:

  • Ao usar a camada de canal/ouvinte, cabe ao aplicativo criar e aceitar canais no lado do servidor. Da mesma forma, cabe ao aplicativo criar e abrir canais no lado do cliente. Um aplicativo deve colocar um limite superior nessas operações porque cada canal consome memória e outros recursos limitados, como soquetes. Um aplicativo deve ser especialmente cuidadoso ao criar um canal em resposta a qualquer ação disparada por uma parte remota.
  • Cabe ao aplicativo escrever a lógica para criar canais e aceitá-los. Cada canal consome recursos limitados, como memória e soquetes. Um aplicativo deve ter um limite superior no número de canais que está disposto a aceitar ou uma parte remota pode fazer muitas conexões, levando à OOM e, portanto, à negação de serviço. Ele também deve receber ativamente mensagens dessas conexões usando um pequeno tempo limite. Se nenhuma mensagem for recebida, a operação terá um tempo limite e a conexão deverá ser liberada.
  • Cabe a um aplicativo enviar uma resposta ou uma falha interpretando os cabeçalhos ReplyTo ou FaultTo SOAP. A prática segura é honrar apenas um cabeçalho ReplyTo ou FaultTo que seja "anônimo", o que significa que a conexão existente (TCP, HTTP) ou IP de origem (UDP) deve ser usada para enviar a resposta SOAP. Os aplicativos devem ter extrema cautela ao criar recursos (como um canal) para responder a um endereço diferente, a menos que a mensagem tenha sido assinada por uma parte que possa falar pelo endereço ao qual a resposta está sendo enviada.
  • A validação feita na camada de canal não substitui a integridade de dados obtida por meio da segurança. Um aplicativo deve contar com recursos de segurança do WWSAPI para garantir que ele esteja se comunicando com uma entidade confiável e também deve contar com a segurança para garantir a integridade dos dados.

Da mesma forma, há opções de configuração de mensagem e considerações de design de aplicativo que devem ser avaliadas cuidadosamente ao usar a API de Mensagem WWSAPI:

  • O tamanho do heap usado para armazenar os cabeçalhos de uma mensagem pode ser configurado usando a propriedade WS_MESSAGE_PROPERTY_HEAP_PROPERTIES . Aumentar esse valor permite que mais memória seja consumida pelos cabeçalhos da mensagem, o que pode levar ao OOM.
  • O usuário do objeto de mensagem deve perceber que as APIs de acesso de cabeçalho são O(n) em relação ao número de cabeçalhos na mensagem, pois verificam se há duplicatas. Designs que exigem muitos cabeçalhos em uma mensagem podem levar ao uso excessivo da CPU.
  • O número máximo de cabeçalhos em uma mensagem pode ser configurado usando a propriedade WS_MESSAGE_PROPERTY_MAX_PROCESSED_HEADERS . Há também um limite implícito com base no tamanho do heap da mensagem. O aumento desses dois valores permite que mais cabeçalhos estejam presentes, o que compõe o tempo necessário para encontrar um cabeçalho (ao usar as APIs de acesso de cabeçalho).