Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Le protocole WebSocket permet une communication bidirectionnelle entre un client et un hôte distant. La System.Net.WebSockets.ClientWebSocket expose la possibilité d’établir une connexion WebSocket via une poignée de main d'ouverture et est créée et envoyée par la méthode ConnectAsync
.
Différences entre HTTP/1.1 et HTTP/2 WebSockets
WebSockets sur HTTP/1.1 utilise une seule connexion TCP. Il est donc géré par des en-têtes à l’échelle de la connexion, pour plus d’informations, consultez RFC 6455. Prenons l’exemple suivant de la façon d’établir WebSocket sur 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);
Une approche différente doit être adoptée avec HTTP/2 en raison de sa nature multiplexante. Les WebSockets sont établis par flux, pour plus d’informations, consultez RFC 8441. Avec HTTP/2, il est possible d'utiliser une connexion pour plusieurs flux WebSocket avec des flux HTTP ordinaires et d'étendre l'utilisation plus efficace de HTTP/2 du réseau aux WebSockets. Il y a une surcharge spéciale de ConnectAsync(Uri, HttpMessageInvoker, CancellationToken) qui accepte un HttpMessageInvoker pour pouvoir réutiliser les connexions regroupées existantes :
using SocketsHttpHandler handler = new();
using ClientWebSocket ws = new();
await ws.ConnectAsync(uri, new HttpMessageInvoker(handler), cancellationToken);
Configurer la version et la stratégie HTTP
Par défaut, ClientWebSocket
utilise HTTP/1.1 pour envoyer une poignée de main initiale et autorise par défaut la rétrogradation. Dans .NET 7, les WebSockets sur HTTP/2 sont disponibles. Il peut être modifié avant d’appeler 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);
Options incompatibles
ClientWebSocket
possède des propriétés System.Net.WebSockets.ClientWebSocketOptions que l’utilisateur peut configurer avant l’établissement de la connexion. Toutefois, lorsque HttpMessageInvoker
est fourni, il possède également ces propriétés. Pour éviter toute ambiguïté, dans ce cas, les propriétés doivent être définies sur HttpMessageInvoker
et ClientWebSocketOptions
doivent avoir des valeurs par défaut. Sinon, si les ClientWebSocketOptions
sont changées, la surcharge de ConnectAsync
lève une 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);
Compression
Le protocole WebSocket prend en charge la compression par message comme défini dans RFC 7692. Il est contrôlé par System.Net.WebSockets.ClientWebSocketOptions.DangerousDeflateOptions. Lorsqu'elles sont présentes, les options sont envoyées au serveur pendant la phase de poignée de main. Si le serveur prend en charge la déflation par message et que les options sont acceptées, l’instance de ClientWebSocket
sera créée avec la compression activée par défaut pour tous les messages.
using ClientWebSocket ws = new()
{
Options =
{
DangerousDeflateOptions = new WebSocketDeflateOptions()
{
ClientMaxWindowBits = 10,
ServerMaxWindowBits = 10
}
}
};
Importante
Avant d’utiliser la compression, sachez que son activation expose l’application à des attaques de type CRIME/BREACH. Pour plus d’informations, consultez CRIME et BREACH. Il est fortement recommandé de désactiver la compression lors de l’envoi de données contenant des secrets en spécifiant l’indicateur de DisableCompression
pour ces messages.
Stratégies Keep-Alive
Sur .NET 8 et les versions antérieures, la seule stratégie Keep-Alive disponible est Unsolicited PONG. Cette stratégie suffit pour empêcher la connexion TCP sous-jacente de devenir inactive. Toutefois, lorsqu'un hôte distant devient non réactif (par exemple, si un serveur distant se bloque), la seule façon de détecter ces situations avec le PONG non sollicité est de reposer sur le délai d'expiration TCP.
.NET 9 introduit la stratégie Keep-Alive PING/PONG tant attendue, en complétant le paramètre KeepAliveInterval
existant par le nouveau paramètre KeepAliveTimeout
. À compter de .NET 9, la stratégie de Keep-Alive est sélectionnée comme suit :
- Keep-Alive est OFF, si
-
KeepAliveInterval
estTimeSpan.Zero
ouTimeout.InfiniteTimeSpan
-
-
Unsolicited PONG, si
-
KeepAliveInterval
est unTimeSpan
fini positif , -AND- -
KeepAliveTimeout
estTimeSpan.Zero
ouTimeout.InfiniteTimeSpan
-
-
PING/PONG, si
-
KeepAliveInterval
est unTimeSpan
fini positif , -AND- -
KeepAliveTimeout
est unTimeSpan
fini positif
-
La valeur de KeepAliveTimeout
par défaut est Timeout.InfiniteTimeSpan
. Par conséquent, le comportement de Keep-Alive par défaut reste cohérent entre les versions de .NET.
Si vous utilisez ClientWebSocket
, la valeur de ClientWebSocketOptions.KeepAliveInterval par défaut est WebSocket.DefaultKeepAliveInterval (généralement 30 secondes). Cela signifie que ClientWebSocket
a la Keep-Alive activée par défaut, avec PONG non sollicité comme stratégie par défaut.
Si vous souhaitez passer à la stratégie PING/PONG, le remplacement de ClientWebSocketOptions.KeepAliveTimeout suffit :
var ws = new ClientWebSocket();
ws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(20);
await ws.ConnectAsync(uri, cts.Token);
Pour une WebSocket
de base, le Keep-Alive est DÉSACTIVÉ par défaut. Si vous souhaitez utiliser la stratégie PING/PONG, les WebSocketCreationOptions.KeepAliveInterval et les WebSocketCreationOptions.KeepAliveTimeout doivent être définis :
var options = new WebSocketCreationOptions()
{
KeepAliveInterval = WebSocket.DefaultKeepAliveInterval,
KeepAliveTimeout = TimeSpan.FromSeconds(20)
};
var ws = WebSocket.CreateFromStream(stream, options);
Si on utilise la stratégie Unsolicited PONG, les images PONG sont utilisées comme pulsation unidirectionnelle. Ils envoient régulièrement avec des intervalles de KeepAliveInterval
, peu importe si le point de terminaison distant communique ou non.
Si la stratégie PING/PONG est active, une image PING est envoyée après le temps KeepAliveInterval
écoulé depuis la dernière communication à partir du point de terminaison distant. Chaque frame PING contient un jeton entier à associer à la réponse PONG attendue. Si aucune réponse PONG n’est arrivée après que KeepAliveTimeout
se soit écoulé, le point de terminaison distant est considéré comme sans réponse, et la connexion WebSocket est automatiquement interrompue.
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 le délai d’expiration expire, une ReceiveAsync
en attente lève une 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()
...
Continuez à lire pour traiter les PONGs
Remarque
Actuellement, WebSocket
traite uniquement les images entrantes pendant qu’il existe un ReceiveAsync
en attente.
Importante
Si vous souhaitez utiliser le délai d’expiration Keep-Alive, il est crucial que les réponses PONG soient traitées rapidement. Même si le point de terminaison distant est actif et envoie correctement la réponse PONG, si le WebSocket
ne traite pas les images entrantes, le mécanisme Keep-Alive peut provoquer un abandon « faux positif ». Ce problème peut se produire si la trame PONG n’est jamais récupérée à partir du flux de transport avant l’expiration du délai.
Pour éviter de perdre de bonnes connexions, les utilisateurs sont invités à conserver une lecture en attente sur tous les WebSockets qui ont le délai d’expiration Keep-Alive configuré.