Información general sobre la capa de canal

La capa de canal proporciona una abstracción del canal de transporte, así como los mensajes enviados en el canal. También incluye funciones para la serialización de tipos de datos de C hacia y desde estructuras SOAP. La capa de canal permite el control total de las comunicaciones mediante mensajes que constan de datos enviados o recibidos y que contienen cuerpos y encabezados, y canales que abstraen protocolos de intercambio de mensajes y proporcionan propiedades para personalizar la configuración.

Message

Un mensaje es un objeto que encapsula los datos de red, en concreto, los datos que se transmiten o reciben a través de una red. Soap define la estructura del mensaje, con un conjunto discreto de encabezados y un cuerpo del mensaje. Los encabezados se colocan en un búfer de memoria y el cuerpo del mensaje se lee o escribe mediante una API de secuencia.

Diagrama que muestra el encabezado y el cuerpo de un mensaje.

Aunque el modelo de datos de un mensaje siempre es el modelo de datos XML, el formato de conexión real es flexible. Antes de transmitir un mensaje, se codifica mediante una codificación determinada (por ejemplo, Texto, Binario o MTOM). Consulte WS_ENCODING para obtener más información sobre las codificaciones.

Diagrama que muestra varios formatos de codificación de mensajes.

Canal

Un canal es un objeto que se usa para enviar y recibir mensajes en una red entre dos o más puntos de conexión.

Los canales tienen datos asociados que describen cómo abordar el mensaje cuando se envía. Enviar un mensaje en un canal es como colocarlo en un chute: el canal incluye la información donde debe ir el mensaje y cómo llegar allí.

Diagrama en el que se muestran los canales de los mensajes.

Los canales se clasifican en tipos de canal. Un tipo de canal especifica qué dirección pueden fluir los mensajes. El tipo de canal también identifica si el canal es con sesión o sin sesión. Una sesión se define como una forma abstracta de correlacionar mensajes entre dos o más partes. Un ejemplo de un canal con sesión es un canal TCP, que usa la conexión TCP como implementación de sesión concreta. Un ejemplo de un canal sin sesión es UDP, que no tiene un mecanismo de sesión subyacente. Aunque HTTP tiene conexiones TCP subyacentes, este hecho no se expone directamente a través de esta API y, por tanto, HTTP también se considera un canal sin sesión.

Diagrama que muestra los tipos de canal con sesión y sin sesión.

Aunque los tipos de canal describen la dirección y la información de sesión de un canal, no especifican cómo se implementa el canal. ¿Qué protocolo debe usar el canal? ¿Qué tan difícil debe intentar el canal entregar el mensaje? ¿Qué tipo de seguridad se usa? ¿Es singlecast o multidifusión? Esta configuración se conoce como el "enlace" del canal. El enlace consta de lo siguiente:

Diagrama que muestra una lista de propiedades de canal.

Agente de escucha

Para empezar a comunicarse, el cliente crea un objeto Channel. ¿Pero cómo obtiene el servicio su objeto Channel? Para ello, crea un agente de escucha. La creación de un agente de escucha requiere la misma información de enlace necesaria para crear un canal. Una vez creado un agente de escucha, la aplicación puede aceptar canales desde el agente de escucha. Dado que la aplicación puede estar detrás de la aceptación de canales, los agentes de escucha normalmente mantienen una cola de canales que están listos para aceptarse (hasta una cuota).

Diagrama que muestra los canales en la cola del agente de escucha.

Iniciando la comunicación (cliente)

Para iniciar la comunicación en el cliente, use la siguiente secuencia.

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

Aceptar comunicación (servidor)

Para aceptar las comunicaciones entrantes en el servidor, use la siguiente secuencia.

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

Enviar mensajes (cliente o servidor)

Para enviar mensajes, use la siguiente secuencia.

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

La función WsSendMessage no permite el streaming y supone que el cuerpo solo contiene un elemento. Para evitar estas restricciones, use la siguiente secuencia en lugar 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

La función WsWriteBody usa la serialización para escribir los elementos body. Para escribir los datos directamente en el escritor XML, use la siguiente secuencia en lugar 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?        

La función WsAddCustomHeader usa la serialización para establecer los encabezados en el búfer de encabezados del mensaje. Para usar el escritor XML para escribir un encabezado, use la siguiente secuencia en lugar 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

Recepción de mensajes (cliente o servidor)

Para recibir mensajes, use la siguiente secuencia.

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

La función WsReceiveMessage no permite el streaming y supone que el cuerpo solo contiene un elemento y que el tipo del mensaje (acción y esquema del cuerpo) se conoce por adelantado. Para evitar estas restricciones, use la siguiente secuencia en lugar 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

La función WsReadBody usa la serialización para leer los elementos del cuerpo. Para leer los datos directamente desde el Lector XML, use la siguiente secuencia en lugar 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

Las funciones WsGetCustomHeader usan la serialización para obtener los encabezados del búfer de encabezados del mensaje. Para usar el Lector XML para leer un encabezado, use la siguiente secuencia en lugar 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
    }
}                

Solicitud de respuesta (cliente)

La realización de una solicitud-respuesta en el cliente se puede realizar con la siguiente secuencia.

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

La función WsRequestReply supone un único elemento para el cuerpo de los mensajes de solicitud y respuesta, y que el tipo de mensaje (acción y esquema del cuerpo) se conocen por adelantado. Para evitar estas limitaciones, el mensaje de solicitud y respuesta se puede enviar manualmente, como se muestra en la secuencia siguiente. Esta secuencia coincide con la secuencia anterior para enviar y recibir un mensaje, excepto cuando se indique.

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                

Solicitud de respuesta (servidor)

Para recibir un mensaje de solicitud en el servidor, use la misma secuencia que se describe en la sección anterior sobre la recepción de mensajes.

Para enviar una respuesta o un mensaje de error, use la siguiente secuencia.

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

La función WsSendReplyMessage supone un único elemento en el cuerpo y no permite el streaming. Para evitar estas limitaciones, use la siguiente secuencia. Se trata de la misma secuencia anterior para enviar un mensaje, pero usa WS_REPLY_MESSAGE en lugar de WS_BLANK_MESSAGE al 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

Modelos de intercambio de mensajes

El WS_CHANNEL_TYPE dicta el patrón de intercambio de mensajes posible para un canal determinado. El tipo admitido varía según el enlace, como se indica a continuación:

Bucles de mensajes

Para cada patrón de intercambio de mensajes, hay un "bucle" específico que se puede usar para enviar o recibir mensajes. El bucle describe el orden legal de las operaciones necesarias para enviar o recibir varios mensajes. Los bucles se describen a continuación como producciones gramaticales. El término "end" es una recepción en la que se devuelve WS_S_END (consulte Valores devueltos de servicios web de Windows), lo que indica que no hay más mensajes disponibles en el canal. La producción paralela especifica que para parallel(x & y) esa operación se puede realizar simultáneamente con y.

Los bucles siguientes se usan en el 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

Los bucles siguientes se usan en el 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

El uso del WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING en el servidor requiere una recepción correcta antes de permitir el envío incluso con un canal de tipo WS_CHANNEL_TYPE_DUPLEX_SESSION. Después de la primera recepción. se aplica el bucle normal.

Tenga en cuenta que los canales de tipo WS_CHANNEL_TYPE_REQUEST y WS_CHANNEL_TYPE_REPLY se pueden usar para enviar y recibir mensajes unidireccionales (así como el patrón estándar de solicitud-respuesta). Esto se logra cerrando el canal de respuesta sin enviar una respuesta. En este caso, no se recibirá ninguna respuesta en el canal de solicitud. El valor devuelto WS_S_END Usar el WS_SECURITY_CONTEXT_MESSAGE_SECURITY_BINDING en el servidor requiere una recepción correcta antes de permitir el envío incluso con un canal de tipo WS_CHANNEL_TYPE_DUPLEX_SESSION. Después de que se aplique el primer bucle regular.

se devolverá, lo que indica que no hay ningún mensaje disponible.

Los bucles de cliente o servidor se pueden realizar en paralelo entre sí mediante el uso de varias instancias de canal.

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

Filtrado de mensajes

Un canal de servidor puede filtrar los mensajes recibidos que no están diseñados para la aplicación, como los mensajes que establecen un contexto de seguridad. En ese caso , WS_S_END se devolverán desde WsReadMessageStart y no habrá ningún mensaje de aplicación disponible en ese canal. Sin embargo, esto no indica que el cliente pretende finalizar la comunicación con el servidor. Es posible que haya más mensajes disponibles en otro canal. Consulte WsShutdownSessionChannel.

Cancelación

La función WsAbortChannel se usa para cancelar la E/S pendiente de un canal. Esta API no esperará a que se completen las operaciones de E/S. Consulte el diagrama de estado de WS_CHANNEL_STATE y la documentación de WsAbortChannel para obtener más información.

La API WsAbortListener se usa para cancelar la E/S pendiente de un agente de escucha. Esta API no esperará a que se completen las operaciones de E/S. La anulación de un agente de escucha hará que también se anulen las aceptaciones pendientes. Consulte el diagrama de estado de WS_LISTENER_STATE y WsAbortListener para obtener más información.

TCP

El WS_TCP_CHANNEL_BINDING admite SOAP a través de TCP. La especificación SOAP sobre TCP se basa en el mecanismo de tramas de .NET.

No se admite el uso compartido de puertos en esta versión. Cada agente de escucha que se abra debe usar un número de puerto diferente.

UDP

El WS_UDP_CHANNEL_BINDING admite SOAP a través de UDP.

Hay varias limitaciones con el enlace UDP:

  • No hay compatibilidad con la seguridad.
  • Los mensajes se pueden perder o duplicar.
  • Solo se admite una codificación: WS_ENCODING_XML_UTF8.
  • Los mensajes se limitan fundamentalmente a 64 k y, con frecuencia, tienen una mayor posibilidad de perderse si el tamaño supera la MTU de la red.

HTTP

El WS_HTTP_CHANNEL_BINDING admite SOAP a través de HTTP.

Para controlar encabezados específicos http en el cliente y el servidor, consulte WS_HTTP_MESSAGE_MAPPING.

Para enviar y recibir mensajes que no son SOAP en el servidor, use WS_ENCODING_RAW para WS_CHANNEL_PROPERTY_ENCODING.

NAMEDPIPES

El WS_NAMEDPIPE_CHANNEL_BINDING admite SOAP sobre canalizaciones con nombre, lo que permite la comunicación con el servicio Windows Communication Foundation (WCF) mediante NetNamedPipeBinding.

Correlación de mensajes de solicitud y respuesta

Los mensajes de solicitud y respuesta se correlacionan de dos maneras:

  • La correlación se realiza mediante el canal como mecanismo de correlación. Por ejemplo, cuando se usa WS_ADDRESSING_VERSION_TRANSPORT y WS_HTTP_CHANNEL_BINDING la respuesta del mensaje de solicitud se correlaciona con la solicitud por el hecho de que es el cuerpo de entidad de la respuesta HTTP.
  • La correlación se realiza mediante los encabezados MessageID y RelatesTo. Este mecanismo se usa con WS_ADDRESSING_VERSION_1_0 y WS_ADDRESSING_VERSION_0_9 (incluso cuando se usa WS_HTTP_CHANNEL_BINDING). En este caso, el mensaje de solicitud incluye el encabezado MessageID. El mensaje de respuesta incluye un encabezado RelatesTo que tiene el valor del encabezado MessageID de la solicitud. El encabezado RelatesTo permite al cliente correlacionar una respuesta con una solicitud que envió.

Las siguientes API de capa de canal usan automáticamente los mecanismos de correlación adecuados en función del WS_ADDRESSING_VERSION del canal.

Si no se usan estas API, los encabezados se pueden agregar y tener acceso manualmente mediante WsSetHeader o WsGetHeader.

Canales personalizados y agentes de escucha

Si el conjunto predefinido de enlaces de canal no satisface las necesidades de la aplicación, se puede definir un canal personalizado y una implementación del agente de escucha especificando WS_CUSTOM_CHANNEL_BINDING al crear el canal o el agente de escucha. La implementación real del canal o agente de escucha se especifica como un conjunto de devoluciones de llamada a través de propiedades WS_CHANNEL_PROPERTY_CUSTOM_CHANNEL_CALLBACKS o WS_LISTENER_PROPERTY_CUSTOM_LISTENER_CALLBACKS . Una vez creado un canal personalizado o un agente de escucha, el resultado es un objeto WS_CHANNEL o WS_LISTENER que se puede usar con las API existentes.

También se puede usar un canal personalizado y un agente de escucha con proxy de servicio y host de servicio especificando el valor de WS_CUSTOM_CHANNEL_BINDING en la enumeración WS_CHANNEL_BINDING y las propiedades WS_CHANNEL_PROPERTY_CUSTOM_CHANNEL_CALLBACKS y WS_LISTENER_PROPERTY_CUSTOM_LISTENER_CALLBACKS al crear el proxy de servicio o el host de servicio.

Seguridad

El canal permite limitar la cantidad de memoria usada para diversos aspectos de las operaciones a través de propiedades como:

Estas propiedades tienen valores predeterminados que son conservadores y seguros para la mayoría de los escenarios. Los valores predeterminados y las modificaciones en ellos deben evaluarse cuidadosamente frente a posibles vectores de ataque que pueden provocar la denegación de servicio por parte de un usuario remoto.

El canal permite establecer valores de tiempo de espera para varios aspectos de las operaciones mediante propiedades como:

Estas propiedades tienen valores predeterminados que son conservadores y seguros para la mayoría de los escenarios. El aumento de los valores de tiempo de espera aumenta la cantidad de tiempo que una entidad remota puede contener un recurso local activo, como la memoria, los sockets y los subprocesos que realizan E/S sincrónicas. Una aplicación debe evaluar los valores predeterminados y tener precaución al aumentar un tiempo de espera, ya que puede abrir posibles vectores de ataque que pueden provocar la denegación de servicio desde un equipo remoto.

Algunas de las otras opciones de configuración y consideraciones de diseño de aplicaciones que se deben evaluar cuidadosamente al usar WWSAPI Channel API:

  • Cuando se usa la capa de canal/agente de escucha, depende de la aplicación crear y aceptar canales en el lado servidor. Del mismo modo, depende de la aplicación crear y abrir canales en el lado cliente. Una aplicación debe colocar un límite superior en estas operaciones porque cada canal consume memoria y otros recursos limitados, como sockets. Una aplicación debe tener especial cuidado al crear un canal en respuesta a cualquier acción desencadenada por una entidad remota.
  • La aplicación debe escribir la lógica para crear canales y aceptarlos. Cada canal consume recursos limitados, como memoria y sockets. Una aplicación debe tener un límite superior en el número de canales que está dispuesto a aceptar, o una parte remota podría realizar muchas conexiones, lo que conduce a OOM y, por tanto, la denegación de servicio. También debe recibir activamente mensajes de esas conexiones con un tiempo de espera pequeño. Si no se reciben mensajes, la operación agotará el tiempo de espera y se debe liberar la conexión.
  • Es necesario que una aplicación envíe una respuesta o un error interpretando los encabezados SOAP ReplyTo o FaultTo. La práctica segura es respetar solo un encabezado ReplyTo o FaultTo que sea "anónimo", lo que significa que la conexión existente (TCP, HTTP) o ip de origen (UDP) se debe usar para enviar la respuesta SOAP. Las aplicaciones deben tener extrema precaución al crear recursos (por ejemplo, un canal) con el fin de responder a una dirección diferente, a menos que un usuario haya firmado el mensaje que pueda hablar para la dirección a la que se envía la respuesta.
  • La validación realizada en la capa de canal no reemplaza la integridad de los datos lograda a través de la seguridad. Una aplicación debe basarse en las características de seguridad de WWSAPI para asegurarse de que se está comunicando con una entidad de confianza y también debe confiar en la seguridad para garantizar la integridad de los datos.

Del mismo modo, hay opciones de configuración de mensajes y consideraciones de diseño de aplicaciones que se deben evaluar cuidadosamente al usar WWSAPI Message API:

  • El tamaño del montón usado para almacenar los encabezados de un mensaje se puede configurar mediante la propiedad WS_MESSAGE_PROPERTY_HEAP_PROPERTIES . Aumentar este valor permite que los encabezados del mensaje consuman más memoria, lo que puede dar lugar a OOM.
  • El usuario del objeto message debe darse cuenta de que las API de acceso de encabezado son O(n) con respecto al número de encabezados del mensaje, ya que comprueban si hay duplicados. Los diseños que requieren muchos encabezados en un mensaje pueden provocar un uso excesivo de la CPU.
  • El número máximo de encabezados de un mensaje se puede configurar mediante la propiedad WS_MESSAGE_PROPERTY_MAX_PROCESSED_HEADERS . También hay un límite implícito basado en el tamaño del montón del mensaje. Aumentar ambos valores permite que haya más encabezados presentes, lo que compone el tiempo necesario para encontrar un encabezado (cuando se usan las API de acceso de encabezado).