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 abstraem protocolos de troca de mensagens e fornecem propriedades para personalizar as 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 pelo 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.

Diagrama mostrando o cabeçalho e o corpo de uma mensagem.

Embora o modelo de dados de uma mensagem seja sempre o modelo de dados XML, o formato de transmissão real é flexível. Antes que uma mensagem seja 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.

Diagrama mostrando vários formatos de codificação de mensagens.

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 endereçar a mensagem quando ela é enviada. Enviar uma mensagem em um canal é como colocá-la em um pára-quedas — o canal inclui as informações para onde a mensagem deve ir e como obtê-la lá.

Diagrama mostrando canais para mensagens.

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 maneira abstrata de correlacionar mensagens entre duas ou mais partes. Um exemplo de canal com sessão é um canal TCP, que usa a conexão TCP como a implementação concreta da sessão. Um exemplo de um canal sem sessão é o UDP, que não tem um mecanismo de sessão subjacente. Embora o 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.

Diagrama mostrando tipos de canal sem sessão e sessão.

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 chamadas de "associação" do canal. A associação consiste no seguinte:

Diagrama mostrando uma lista de propriedades do canal.

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. A criação de um ouvinte requer as mesmas informações de associação necessárias para criar um canal. Depois que um ouvinte tiver sido criado, o aplicativo poderá Aceitar Canais do Ouvinte. Como o aplicativo pode ficar atrasado na aceitação de canais, os ouvintes normalmente mantêm uma fila de canais que estão prontos para aceitar (até alguma cota).

Diagrama mostrando canais na fila ouvinte.

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 gravar 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 de 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
    }
}                

Solicitação de Resposta (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) é conhecido 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 anotado.

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                

Solicitação de Resposta (servidor)

Para receber uma mensagem de solicitação no servidor, use a mesma sequência descrita na seção anterior sobre o recebimento de 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 troca de mensagens

O WS_CHANNEL_TYPE determina 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 Valores retornados dos Serviços Web do Windows), 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

O uso do 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. Após o primeiro receber, o loop regular se aplica.

será retornado, indicando que 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 de 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 todas as aceitações pendentes também sejam anuladas. 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 por TCP. A especificação SOAP over TCP baseia-se no mecanismo de enquadramento do .NET.

Não há suporte para o compartilhamento de porta 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 apenas para 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 por 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 a 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 que é 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 correlacione uma resposta com uma solicitação enviada.

As APIs de 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 ou ouvinte personalizado é 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 a eles 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 que podem causar negação de serviço de um computador remoto.

Algumas das outras opções de configuração e considerações de design do aplicativo que devem ser cuidadosamente avaliadas ao usar a API de Canal 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 atingirá o tempo limite e a conexão deverá ser liberada.
  • Cabe a um aplicativo enviar uma resposta ou falha interpretando os cabeçalhos ReplyTo ou FaultTo SOAP. A prática segura é respeitar 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 para o qual a resposta está sendo enviada.
  • A validação feita na camada de canal não substitui a integridade dos 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 mensagens e considerações de design de aplicativo que devem ser cuidadosamente avaliadas 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, já que marcar para 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 . Também há um limite implícito com base no tamanho do heap da mensagem. Aumentar esses dois valores permite que mais cabeçalhos estejam presentes, o que aumenta o tempo necessário para encontrar um cabeçalho (ao usar as APIs de acesso de cabeçalho).