Partilhar via


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 consistindo em dados enviados ou recebidos e contendo corpos e cabeçalhos, e 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 por SOAP, com um conjunto discreto de cabeçalhos e um corpo de 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 é 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.

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

Canal

Um canal é um objeto usado para enviar e receber mensagens numa rede entre dois ou mais terminais.

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 chute - o canal inclui as informações para onde a mensagem deve ir e como levá-la até lá.

Diagrama mostrando canais para mensagens.

Os canais são categorizados em tipos de canais. Um tipo de canal especifica qual direção as mensagens podem fluir. O tipo de canal também identifica se o canal é com 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 utiliza a conexão TCP como a implementação concreta da sessão. Um exemplo de um canal sem sessã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, o HTTP também é considerado um canal sem sessão.

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

Embora os tipos de canal descrevam a direção e as informações de sessão de um canal, eles não especificam como o canal é implementado. Qual protocolo o canal deve usar? Até que ponto o canal deve tentar passar a mensagem? Que tipo de segurança é utilizada? É singlecast ou multicast? Essas configurações são referidas como a "ligação" do canal. A ligaçã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 o objeto Channel? Ele realiza isso criando um Listener. Criar um ouvinte requer as mesmas informações de vinculação necessárias para criar um canal. Uma vez que um ouvinte tenha sido criado, o aplicativo pode 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).

Diagrama mostrando canais na fila do Ouvinte.

Iniciando a Comunicação (cliente)

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

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

Envio de 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 assume que o corpo contém 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 utiliza serialização para escrever os elementos do corpo. Para gravar os dados diretamente no XML Writer, 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çãoWsAddCustomHeader usa serialização para definir os cabeçalhos para o buffer de cabeçalho da mensagem. Para usar o XML Writer 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 application header end element

Receber 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 funcionalidade WsReadBody usa a serialização para ler os elementos do corpo. Para ler os dados diretamente do XML Reader, 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 a partir do buffer de cabeçalhos da mensagem. Para usar o do 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
    }
}                

Solicitar resposta (cliente)

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

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 assume um único elemento para o corpo das mensagens de solicitação e resposta, e que o tipo de mensagem (ação e esquema do corpo) são conhecidos antecipadamente. Para evitar essas limitações, a mensagem de solicitação e resposta pode ser enviada manualmente, conforme mostrado na sequência a seguir. Esta sequência corresponde à sequência anterior para enviar e receber uma mensagem, exceto onde indicado.

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                

Solicitar 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 de 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 assume um único elemento no corpo e não suporta transmissão contínua de dados. 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 dita o padrão de troca de mensagens possível para um determinado canal. O tipo suportado varia de acordo com a ligação da seguinte forma:

Loops de mensagens

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 'fim' refere-se a um ponto de receção onde WS_S_END é retornado (Consulte Valores de Retorno dos Windows Web Services), indicando que não existem 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 seguintes loops 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 seguintes loops 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. aplica-se o loop regular.

Observe que os 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 padrão solicitação-resposta). Isso é feito fechando o canal de resposta sem enviar uma resposta. Neste caso, não haverá resposta recebida no canal de solicitação. O valor de retorno 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 a primeira receção, aplica-se o loop regular.

será devolvido, indicando que não há mensagem disponível.

Os loops de cliente ou servidor podem ser feitos em paralelo uns com os outros 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 mensagens

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 casoWS_S_END será retornado do WsReadMessageStart e nenhuma mensagem do 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 IO pendentes do canal. Esta API não aguardará a conclusão da(s) operação(ões) de E/S. Consulte o diagrama de estado WS_CHANNEL_STATE e a documentação do WsAbortChannel para obter mais informações.

O WsAbortListener API é usado para cancelar E/S pendentes para um ouvinte. Esta API não aguardará a conclusão da(s) operação(ões) de E/S. Abortar um ouvinte fará com que qualquer aceitação pendente também seja abortada. Consulte o diagrama de estado WS_LISTENER_STATE e WsAbortListener para obter mais informações.

TCP

O WS_TCP_CHANNEL_BINDING suporta SOAP sobre TCP. A especificação SOAP sobre TCP baseia-se no mecanismo de enquadramento .NET.

O compartilhamento de portas não é suportado nesta versão. Cada ouvinte aberto deve usar um número de porta diferente.

UDP

O WS_UDP_CHANNEL_BINDING suporta SOAP sobre UDP.

Há uma série de limitações com a vinculação UDP:

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

HTTP

O WS_HTTP_CHANNEL_BINDING suporta SOAP sobre HTTP.

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

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

NAMEDPIPES

O WS_NAMEDPIPE_CHANNEL_BINDING suporta SOAP sobre pipes nomeados, permitindo a comunicação com o serviço Windows Communication Foundation (WCF) 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. Este mecanismo é utilizado com WS_ADDRESSING_VERSION_1_0 e WS_ADDRESSING_VERSION_0_9 (mesmo quando se utiliza 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 da camada de canal a seguir usam automaticamente os mecanismos de correlação apropriados com base na 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 e ouvintes personalizados

Se o conjunto predefinido de ligações de canal não atender às necessidades do aplicativo, uma implementação personalizada de canal e ouvinte pode ser definida especificando WS_CUSTOM_CHANNEL_BINDING ao criar o canal ou ouvinte. A implementação real do canal/ouvinte é especificada como um conjunto de funções de retorno através das 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 e ouvinte personalizados também podem ser usados com o Proxy de Serviço e de 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 Host de Serviço.

Segurança

O canal permite limitar a quantidade de memória usada para vários aspetos das operações através 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 contra potenciais vetores de ataque que possam causar negação de serviço por um usuário remoto.

O canal permite definir valores de tempo limite para vários aspetos das operações através 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 potenciais 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 de aplicativo que devem ser cuidadosamente avaliadas ao usar a API do 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 acionada 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 ao OOM e, portanto, à negação de serviço. Ele também deve receber ativamente mensagens dessas conexões usando um pequeno intervalo de tempo. Se nenhuma mensagem for recebida, a operação expirará e a conexão deverá ser liberada.
  • Cabe a um aplicativo enviar uma resposta ou falha interpretando os cabeçalhos SOAP ReplyTo ou FaultTo. A prática segura é honrar apenas um cabeçalho ReplyTo ou FaultTo que é "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 alcançada através da segurança. Um aplicativo deve confiar em recursos de segurança do WWSAPI para garantir que está se comunicando com uma entidade confiável e também deve confiar na 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 da pilha usada 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 message deve perceber que as APIs de acesso ao cabeçalho são O(n) em relação ao número de cabeçalhos na mensagem, uma vez que 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 da pilha da mensagem. Aumentar ambos os 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 ao cabeçalho).