Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Протокол WebSocket обеспечивает двустороннее взаимодействие между клиентом и удаленным узлом.
System.Net.WebSockets.ClientWebSocket предоставляет возможность установить подключение WebSocket через открывающее рукопожатие, которое создается и отправляется методом ConnectAsync
.
Различия в HTTP/1.1 и HTTP/2 WebSockets
WebSockets по протоколу HTTP/1.1 используют одно TCP-подключение, поэтому управление осуществляется заголовками всего соединения. Дополнительные сведения см. в статье RFC 6455. Рассмотрим следующий пример установки WebSocket по протоколу 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);
Другой подход должен применяться с протоколом HTTP/2 из-за его мультиплексирования. WebSockets устанавливаются для каждого потока, для получения дополнительной информации см. RFC 8441. С помощью HTTP/2 можно использовать одно подключение для нескольких веб-сокет-потоков вместе с обычными HTTP-потоками и распространить более эффективное использование сети HTTP/2 на WebSockets. Существует специальная перегрузка ConnectAsync(Uri, HttpMessageInvoker, CancellationToken), которая принимает HttpMessageInvoker, позволяя повторно использовать существующие подключения из пула.
using SocketsHttpHandler handler = new();
using ClientWebSocket ws = new();
await ws.ConnectAsync(uri, new HttpMessageInvoker(handler), cancellationToken);
Настройка версии и политики HTTP
По умолчанию ClientWebSocket
использует HTTP/1.1 для отправки открытого подтверждения и позволяет уменьшить уровень. В .NET 7 доступны веб-сокеты по протоколу HTTP/2. Его можно изменить перед вызовом 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);
Несовместимые параметры
ClientWebSocket
имеет свойства System.Net.WebSockets.ClientWebSocketOptions, которые пользователь может настроить до установки подключения. Однако при предоставлении HttpMessageInvoker
он также имеет эти свойства. Чтобы избежать неоднозначности, в этом случае свойства следует задать в HttpMessageInvoker
, а ClientWebSocketOptions
должны иметь значения по умолчанию. В противном случае, если ClientWebSocketOptions
изменено, перегрузка ConnectAsync
вызовет 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);
Сжатие
Протокол WebSocket поддерживает сжатие на уровне каждого сообщения, как определено в RFC 7692. Он контролируется System.Net.WebSockets.ClientWebSocketOptions.DangerousDeflateOptions. При наличии параметры отправляются на сервер во время этапа подтверждения. Если сервер поддерживает дефланцию по сообщению и принимает параметры, экземпляр ClientWebSocket
будет создан со сжатием, включенным по умолчанию для всех сообщений.
using ClientWebSocket ws = new()
{
Options =
{
DangerousDeflateOptions = new WebSocketDeflateOptions()
{
ClientMaxWindowBits = 10,
ServerMaxWindowBits = 10
}
}
};
Важный
Прежде чем использовать сжатие, пожалуйста, имейте в виду, что его включение делает приложение уязвимым для атак типа CRIME/BREACH. Дополнительную информацию см. в разделах CRIME и BREACH. Настоятельно рекомендуется отключить сжатие при отправке данных, содержащих секреты, указав флаг DisableCompression
для таких сообщений.
стратегии Keep-Alive
На .NET 8 и более ранних версиях доступна только стратегия непрошенного PONGKeep-Alive. Эта стратегия достаточно эффективна для того, чтобы базовое TCP-подключение не простаивало. Однако в случае, когда удаленный узел не отвечает (например, удаленный сервер отключается), единственный способ обнаружения таких ситуаций с непреднамеренного PONG заключается в том, чтобы полагаться на тайм-аут TCP.
.NET 9 представила долгожданную стратегию PING/PONG Keep-Alive, дополняющую существующий KeepAliveInterval
параметр новым KeepAliveTimeout
параметром. Начиная с .NET 9, стратегия Keep-Alive выбрана следующим образом:
- Keep-Alive OFF, если
-
KeepAliveInterval
TimeSpan.Zero
илиTimeout.InfiniteTimeSpan
-
-
непрошенный PONG, если
-
KeepAliveInterval
является положительным конечнымTimeSpan
, -AND- -
KeepAliveTimeout
TimeSpan.Zero
илиTimeout.InfiniteTimeSpan
-
-
PING/PONG, если
-
KeepAliveInterval
является положительным конечнымTimeSpan
, -AND- -
KeepAliveTimeout
является положительным конечнымTimeSpan
-
Значение по умолчанию KeepAliveTimeout
равно Timeout.InfiniteTimeSpan
, поэтому поведение Keep-Alive по умолчанию остается согласованным между версиями .NET.
Если вы используете ClientWebSocket
, то значение ClientWebSocketOptions.KeepAliveInterval по умолчанию задается как WebSocket.DefaultKeepAliveInterval (обычно 30 секунд). Это означает, что ClientWebSocket
по умолчанию имеет Keep-Alive включённым, а в качестве стратегии по умолчанию используется Unsolicited PONG.
Если Вы хотите переключиться на стратегию PING/PONG, достаточно переопределения ClientWebSocketOptions.KeepAliveTimeout.
var ws = new ClientWebSocket();
ws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(20);
await ws.ConnectAsync(uri, cts.Token);
Для базового WebSocket
Keep-Alive по умолчанию имеет значение OFF. Если вы хотите использовать стратегию PING/PONG, необходимо задать как WebSocketCreationOptions.KeepAliveInterval, так и WebSocketCreationOptions.KeepAliveTimeout:
var options = new WebSocketCreationOptions()
{
KeepAliveInterval = WebSocket.DefaultKeepAliveInterval,
KeepAliveTimeout = TimeSpan.FromSeconds(20)
};
var ws = WebSocket.CreateFromStream(stream, options);
Если используется стратегия Unsolicited PONG, кадры PONG используются как однонаправленный сигнал оповещения. Они отправляются регулярно с интервалами KeepAliveInterval
, независимо от того, происходит ли взаимодействие с удаленной конечной точкой или нет.
Если стратегия PING/PONG активна, кадр PING отправляется после того, как прошло KeepAliveInterval
времени с момента () последнего общения () из удаленной конечной точки. Каждый кадр PING содержит целый идентификатор для соответствия с ожидаемым ответом PONG. Если ответ PONG не получен после того, как истекло KeepAliveTimeout
, удаленная конечная точка считается не отвечающей, а подключение WebSocket автоматически прерывается.
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);
Если время ожидания истекает, выдающийся ReceiveAsync
выдает 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()
...
Продолжайте чтение для обработки PONGs
Заметка
В настоящее время WebSocket
обрабатывает входящие кадры во время ожидания ReceiveAsync
.
Важный
Если вы хотите использовать время ожидания Keep-Alive, важно, что ответы PONG быстро обработаны. Даже если удаленная конечная точка жива и правильно отправляет ответ PONG, но WebSocket
не обрабатывает входящие кадры, механизм Keep-Alive может выдавать "ложноположительные" прерывания. Эта проблема может произойти, если кадр PONG не извлекается из транспортного потока до истечения срока ожидания.
Чтобы избежать разрыва хороших подключений, пользователям рекомендуется держать ожидающее чтение во всех WebSockets, для которых настроен таймаут Keep-Alive.