Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El protocolo WebSocket permite la comunicación bidireccional entre un cliente y un host remoto. El System.Net.WebSockets.ClientWebSocket expone la capacidad de establecer la conexión de WebSocket a través de un apretón de manos inicial, se crea y envía mediante el método ConnectAsync
.
Diferencias en HTTP/1.1 y HTTP/2 WebSockets
WebSockets a través de HTTP/1.1 usa una única conexión TCP, por lo que se administra mediante encabezados para toda la conexión, para obtener más información, consulte RFC 6455. Considere el ejemplo siguiente de cómo establecer WebSocket a través de HTTP/1.1:
Uri uri = new("ws://corefx-net-http11.azurewebsites.net/WebSocket/EchoWebSocket.ashx");
using ClientWebSocket ws = new();
await ws.ConnectAsync(uri, default);
var bytes = new byte[1024];
var result = await ws.ReceiveAsync(bytes, default);
string res = Encoding.UTF8.GetString(bytes, 0, result.Count);
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closed", default);
Se debe adoptar un enfoque diferente con HTTP/2 debido a su naturaleza de multiplexación. Los WebSockets se establecen por secuencia. Para más información, consulte RFC 8441. Con HTTP/2 es posible usar una conexión para varios flujos de WebSocket junto con secuencias HTTP normales y extender el uso más eficiente de la red de HTTP/2 a WebSockets. Hay una sobrecarga especial de ConnectAsync(Uri, HttpMessageInvoker, CancellationToken) que acepta un valor HttpMessageInvoker para permitir la reutilización de las conexiones agrupadas existentes:
using SocketsHttpHandler handler = new();
using ClientWebSocket ws = new();
await ws.ConnectAsync(uri, new HttpMessageInvoker(handler), cancellationToken);
Configuración de la versión y la directiva HTTP
De forma predeterminada, ClientWebSocket
usa HTTP/1.1 para enviar un apretón de manos inicial y permite una versión inferior. En .NET 7 están disponibles los sockets web sobre HTTP/2. Sin embargo, se puede cambiar antes de llamar a ConnectAsync
:
using SocketsHttpHandler handler = new();
using ClientWebSocket ws = new();
ws.Options.HttpVersion = HttpVersion.Version20;
ws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
await ws.ConnectAsync(uri, new HttpMessageInvoker(handler), cancellationToken);
Opciones incompatibles
ClientWebSocket
tiene propiedades System.Net.WebSockets.ClientWebSocketOptions que el usuario puede configurar antes de establecer la conexión. Sin embargo, cuando se proporciona HttpMessageInvoker
, también tiene estas propiedades. Para evitar ambigüedad, en ese caso, las propiedades deben establecerse en HttpMessageInvoker
y ClientWebSocketOptions
deben tener valores predeterminados. De lo contrario, si se cambia ClientWebSocketOptions
, la sobrecarga de ConnectAsync
producirá una excepción ArgumentException.
using HttpClientHandler handler = new()
{
CookieContainer = cookies;
UseCookies = cookies != null;
ServerCertificateCustomValidationCallback = remoteCertificateValidationCallback;
Credentials = useDefaultCredentials
? CredentialCache.DefaultCredentials
: credentials;
};
if (proxy is null)
{
handler.UseProxy = false;
}
else
{
handler.Proxy = proxy;
}
if (clientCertificates?.Count > 0)
{
handler.ClientCertificates.AddRange(clientCertificates);
}
HttpMessageInvoker invoker = new(handler);
using ClientWebSocket cws = new();
await cws.ConnectAsync(uri, invoker, cancellationToken);
Compresión
El protocolo WebSocket admite la deflación por mensaje tal como se define en RFC 7692. Esto se controla mediante System.Net.WebSockets.ClientWebSocketOptions.DangerousDeflateOptions. Cuando existen, las opciones se envían al servidor durante la fase de protocolo de enlace. Si el servidor admite la deflación por mensaje y se aceptan las opciones, la instancia de ClientWebSocket
se creará con la compresión habilitada de forma predeterminada para todos los mensajes.
using ClientWebSocket ws = new()
{
Options =
{
DangerousDeflateOptions = new WebSocketDeflateOptions()
{
ClientMaxWindowBits = 10,
ServerMaxWindowBits = 10
}
}
};
Importante
Antes de usar la compresión, tenga en cuenta que habilitarla hace que la aplicación esté sujeta al tipo CRIME/BREACH de ataques, para obtener más información, consulte CRIME y BREACH. Se recomienda desactivar la compresión al enviar datos que contienen secretos especificando la marca DisableCompression
para dichos mensajes.
Estrategias de mantenimiento de la conexión
En .NET 8 y versiones anteriores, la única estrategia para mantener la conexión disponible es la del PONG no solicitado. Esta estrategia es suficiente para evitar que la conexión TCP subyacente se quede inactiva. Sin embargo, en caso de que un host remoto deje de responder (por ejemplo, un servidor remoto se bloquee), la única manera de detectar estas situaciones con PONG sin solicitud previa es depender del tiempo de espera de TCP.
En .NET 9 se introdujo la esperada estrategia de mantenimiento de la conexión PING/PONG, que complementa la configuración de KeepAliveInterval
existente con la nueva configuración de KeepAliveTimeout
. A partir de .NET 9, la estrategia de Keep-Alive se selecciona de la siguiente manera:
- El mantenimiento de la conexión está desactivado, si
-
KeepAliveInterval
esTimeSpan.Zero
oTimeout.InfiniteTimeSpan
-
-
PONG no solicitado, si
-
KeepAliveInterval
es unTimeSpan
finito positivo , -AND- -
KeepAliveTimeout
esTimeSpan.Zero
oTimeout.InfiniteTimeSpan
-
-
PING/PONG, si
-
KeepAliveInterval
es unTimeSpan
finito positivo , -AND- -
KeepAliveTimeout
es unTimeSpan
finito positivo
-
El valor de KeepAliveTimeout
predeterminado es Timeout.InfiniteTimeSpan
, por lo que el comportamiento predeterminado Keep-Alive sigue siendo coherente entre las versiones de .NET.
Si usa ClientWebSocket
, el valor predeterminado de ClientWebSocketOptions.KeepAliveInterval es WebSocket.DefaultKeepAliveInterval (normalmente 30 segundos). Esto significa que ClientWebSocket
tiene la función de mantenimiento de la conexión activada de forma predeterminada y PONG no solicitado es la estrategia predeterminada.
Si desea cambiar a la estrategia PING/PONG, basta con anular ClientWebSocketOptions.KeepAliveTimeout:
var ws = new ClientWebSocket();
ws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(20);
await ws.ConnectAsync(uri, cts.Token);
Para una WebSocket
básica, el Keep-Alive está desactivado de forma predeterminada. Si desea usar la estrategia PING/PONG, debe establecerse tanto WebSocketCreationOptions.KeepAliveInterval como WebSocketCreationOptions.KeepAliveTimeout:
var options = new WebSocketCreationOptions()
{
KeepAliveInterval = WebSocket.DefaultKeepAliveInterval,
KeepAliveTimeout = TimeSpan.FromSeconds(20)
};
var ws = WebSocket.CreateFromStream(stream, options);
Si se utiliza la estrategia de PONG no solicitado, las tramas PONG se utilizan como un latido unidireccional. Se envían periódicamente con intervalos de KeepAliveInterval
, independientemente de si el punto de conexión remoto se comunica o no.
En caso de que la estrategia PING/PONG esté activa, se envía una trama de PING una vez que haya transcurrido KeepAliveInterval
desde la última comunicación desde el punto de conexión remoto. Cada trama de PING contiene un token entero para emparejarlo con la respuesta de PONG esperada. Si no ha llegado ninguna respuesta PONG después de KeepAliveTimeout
transcurrido, el punto de conexión remoto se considera que no responde y la conexión de WebSocket se anula automáticamente.
var ws = new ClientWebSocket();
ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(10);
ws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(10);
await ws.ConnectAsync(uri, cts.Token);
// NOTE: There must be an outstanding read at all times to ensure
// incoming PONGs are processed
var result = await _webSocket.ReceiveAsync(buffer, cts.Token);
Si transcurre el tiempo de espera, un ReceiveAsync
pendiente produce una OperationCanceledException
:
System.OperationCanceledException: Aborted
---> System.AggregateException: One or more errors occurred. (The WebSocket didn't receive a Pong frame in response to a Ping frame within the configured KeepAliveTimeout.) (Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request..)
---> System.Net.WebSockets.WebSocketException (0x80004005): The WebSocket didn't receive a Pong frame in response to a Ping frame within the configured KeepAliveTimeout.
at System.Net.WebSockets.ManagedWebSocket.KeepAlivePingHeartBeat()
...
Seguir leyendo para procesar PONG
Nota
Actualmente, WebSocket
SOLO procesa las tramas entrantes mientras hay un ReceiveAsync
pendiente.
Importante
Si quiere usar el tiempo de espera de mantenimiento de la conexión, es imprescindible que las respuestas de PONG se procesen rápidamente. Incluso si el punto de conexión remoto está activo y envía correctamente la respuesta de PONG, pero el WebSocket
no procesa las tramas entrantes, el mecanismo de mantenimiento de la conexión puede emitir una anulación de "falso positivo". Este problema puede ocurrir si la trama PONG nunca se recoge de la secuencia de transporte antes de que transcurra el tiempo de espera.
Para evitar que se rompan las buenas conexiones, se recomienda a los usuarios que mantengan una lectura pendiente en todos los WebSockets que tengan configurado el tiempo de espera de mantenimiento de la conexión.