Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Protokol WebSocket umožňuje obousměrnou komunikaci mezi klientem a vzdáleným hostitelem.
System.Net.WebSockets.ClientWebSocket odhaluje schopnost navázat připojení WebSocket prostřednictvím zahajovacího handshake, která je vytvořena a odesílána metodou ConnectAsync
.
Rozdíly v protokolech HTTP/1.1 a HTTP/2 WebSocket
WebSockety přes HTTP/1.1 používají jediný TCP spoj, a proto je spravován hlavičkami platnými pro celé připojení; další informace naleznete v RFC 6455. Podívejte se na následující příklad vytvoření protokolu WebSocket přes 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);
Vzhledem ke své multiplexní povaze je třeba zvolit jiný přístup k HTTP/2. WebSockety jsou vytvořeny pro každý datový proud, další informace viz RFC 8441. S protokolem HTTP/2 je možné použít jedno připojení pro více datových proudů webových soketů společně s běžnými datovými proudy HTTP a rozšířit efektivnější využití sítě na webSockety. Existuje speciální přetížení ConnectAsync(Uri, HttpMessageInvoker, CancellationToken), které přijímá HttpMessageInvoker, aby umožnilo opětovné využití stávajících připojení ve fondu:
using SocketsHttpHandler handler = new();
using ClientWebSocket ws = new();
await ws.ConnectAsync(uri, new HttpMessageInvoker(handler), cancellationToken);
Nastavení verze a zásad HTTP
Ve výchozím nastavení ClientWebSocket
používá HTTP/1.1 k odeslání úvodního spojení handshake a umožňuje downgrade. V .NET 7 jsou k dispozici webové sockety přes HTTP/2. Před voláním ConnectAsync
je možné ho změnit:
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);
Nekompatibilní možnosti
ClientWebSocket
má vlastnosti System.Net.WebSockets.ClientWebSocketOptions, které může uživatel nastavit před navázáním připojení. Pokud je však k dispozici HttpMessageInvoker
, má také tyto vlastnosti. Aby nedocházelo k nejednoznačnosti, měly by být vlastnosti nastaveny na HttpMessageInvoker
a ClientWebSocketOptions
by měly mít výchozí hodnoty. V opačném případě, pokud ClientWebSocketOptions
jsou změněny, přetížení ConnectAsync
vyvolá 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);
Komprese
Protokol WebSocket podporuje deflaci na úrovni jednotlivých zpráv, jak je definováno v RFC 7692.
System.Net.WebSockets.ClientWebSocketOptions.DangerousDeflateOptionsto ovládá. Pokud jsou k dispozici, jsou možnosti posílány na server během fáze navázání spojení. Pokud server podporuje kompresi jednotlivých zpráv a volby jsou akceptovány, vytvoří se instance ClientWebSocket
automaticky s povolenou kompresí pro všechny zprávy.
using ClientWebSocket ws = new()
{
Options =
{
DangerousDeflateOptions = new WebSocketDeflateOptions()
{
ClientMaxWindowBits = 10,
ServerMaxWindowBits = 10
}
}
};
Důležitý
Než povolíte kompresi, mějte na paměti, že její zapnutí činí aplikaci zranitelnou vůči útokům typu CRIME/BREACH. Pro více informací viz CRIME a BREACH. Důrazně doporučujeme vypnout kompresi při odesílání dat obsahujících tajné kódy zadáním příznaku DisableCompression
pro tyto zprávy.
strategie Keep-Alive
Ve verzi .NET 8 a starších verzích je jedinou dostupnou strategií Keep-Alive Nevyžádané PONG. Tato strategie je dostatečná k tomu, aby zabránila základnímu připojení TCP v přechodu do nečinného stavu. V případě, že vzdálený hostitel přestane reagovat (například pokud se vzdálený server zhroutí), jediným způsobem, jak takové situace zjistit pomocí nevyžádaného PONG, je spolehnout se na vypršení časového limitu TCP.
.NET 9 zavedla dlouhou požadovanou strategii ping/PONG Keep-Alive, která doplňuje stávající nastavení KeepAliveInterval
novým nastavením KeepAliveTimeout
. Počínaje rozhraním .NET 9 je strategie Keep-Alive vybrána takto:
- Keep-Alive je vypnuto, pokud
-
KeepAliveInterval
jeTimeSpan.Zero
neboTimeout.InfiniteTimeSpan
-
-
Nevyžádaná PONG, pokud
-
KeepAliveInterval
je pozitivní konečnýTimeSpan
, -AND- -
KeepAliveTimeout
jeTimeSpan.Zero
neboTimeout.InfiniteTimeSpan
-
-
ping/pong, pokud
-
KeepAliveInterval
je pozitivní konečnýTimeSpan
, -AND- -
KeepAliveTimeout
je pozitivní konečnýTimeSpan
-
Výchozí hodnota KeepAliveTimeout
je Timeout.InfiniteTimeSpan
, takže výchozí chování Keep-Alive zůstává konzistentní mezi verzemi .NET.
Pokud používáte ClientWebSocket
, výchozí hodnota ClientWebSocketOptions.KeepAliveInterval je WebSocket.DefaultKeepAliveInterval (obvykle 30 sekund). To znamená, že ClientWebSocket
má ve výchozím nastavení Keep-Alive ZAPNUTO s nevyžádaným PONG jako výchozí strategií.
Pokud chcete přepnout na strategii PING/PONG, stačí přepsání ClientWebSocketOptions.KeepAliveTimeout:
var ws = new ClientWebSocket();
ws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(20);
await ws.ConnectAsync(uri, cts.Token);
Pro základní WebSocket
je Keep-Alive ve výchozím nastavení VYPNUTO. Pokud chcete použít strategii PING/PONG, je potřeba nastavit WebSocketCreationOptions.KeepAliveInterval i WebSocketCreationOptions.KeepAliveTimeout:
var options = new WebSocketCreationOptions()
{
KeepAliveInterval = WebSocket.DefaultKeepAliveInterval,
KeepAliveTimeout = TimeSpan.FromSeconds(20)
};
var ws = WebSocket.CreateFromStream(stream, options);
Pokud se používá strategie Unsolicited PONG, rámce PONG se používají jako jednosměrný prezenční signál. Pravidelně odesílali v intervalech KeepAliveInterval
, bez ohledu na to, zda vzdálená cílová stanice komunikuje, nebo ne.
Pokud je strategie PING/PONG aktivní, odešle se rámec PING po uplynutí času KeepAliveInterval
od poslední komunikace ze vzdáleného koncového bodu. Každý rámec PING obsahuje celočíselné tokeny, které se mají spárovat s očekávanou odpovědí PONG. Pokud po uplynutí KeepAliveTimeout
nedorazí žádná odpověď PONG, vzdálený koncový bod se považuje za nereagující a připojení WebSocket se automaticky přeruší.
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);
Pokud časový limit uplyne, nevyrovnaný ReceiveAsync
vyvolá 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()
...
Pokračujte ve čtení pro zpracování PONGů
Poznámka
V současné době WebSocket
zpracovává pouze příchozí rámce, pokud je ReceiveAsync
ve stavu čekání.
Důležitý
Pokud chcete použít časový limit Keep-Alive, je zásadní, že odpovědi PONG musí být okamžitě zpracovány. I když je vzdálený koncový bod naživu a správně odesílá odpověď PONG, ale WebSocket
nezpracovává příchozí rámce, může mechanismus Keep-Alive vydat "falešně pozitivní" přerušení. K tomuto problému může dojít, pokud rám PONG není nikdy vyzvednut z přenosového proudu před vypršením časového limitu.
Aby se zabránilo přerušení dobrých připojení, doporučujeme uživatelům udržovat proces čtení ve stavu čekání na všech WebSocketech, které mají nakonfigurovaný časový limit Keep-Alive.