Skip to content

Commit b200601

Browse files
authored
WebSocket Keep-Alive (#44611)
1 parent c44b3bf commit b200601

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

docs/fundamentals/networking/websockets.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,76 @@ using ClientWebSocket ws = new()
9999

100100
> [!IMPORTANT]
101101
> Before using compression, please be aware that enabling it makes the application subject to CRIME/BREACH type of attacks, for more information, see [CRIME](https://en.wikipedia.org/wiki/CRIME) and [BREACH](https://en.wikipedia.org/wiki/BREACH). It is strongly advised to turn off compression when sending data containing secrets by specifying the `DisableCompression` flag for such messages.
102+
103+
## Keep-Alive strategies
104+
105+
On **.NET 8** and earlier, the only available Keep-Alive strategy is _Unsolicited PONG_. This strategy is enough to keep the underlying TCP connection from idling out. However, in a case when a remote host becomes unresponsive (for example, a remote server crashes), the only way to detect such situations with Unsolicited PONG is to depend on the TCP timeout.
106+
107+
**.NET 9** introduced the long-desired _PING/PONG_ Keep-Alive strategy, complementing the existing `KeepAliveInterval` setting with the new `KeepAliveTimeout` setting. Starting with .NET 9, the Keep-Alive strategy is selected as follows:
108+
109+
1. Keep-Alive is **OFF**, if
110+
- `KeepAliveInterval` is `TimeSpan.Zero` or `Timeout.InfiniteTimeSpan`
111+
2. **Unsolicited PONG**, if
112+
- `KeepAliveInterval` is a positive finite `TimeSpan`, -AND-
113+
- `KeepAliveTimeout` is `TimeSpan.Zero` or `Timeout.InfiniteTimeSpan`
114+
3. **PING/PONG**, if
115+
- `KeepAliveInterval` is a positive finite `TimeSpan`, -AND-
116+
- `KeepAliveTimeout` is a positive finite `TimeSpan`
117+
118+
The default `KeepAliveTimeout` value is `Timeout.InfiniteTimeSpan`, so the default Keep-Alive behavior remains consistent between .NET versions.
119+
120+
If you use `ClientWebSocket`, the default <xref:System.Net.WebSockets.ClientWebSocketOptions.KeepAliveInterval?displayProperty=nameWithType> value is <xref:System.Net.WebSockets.WebSocket.DefaultKeepAliveInterval?displayProperty=nameWithType> (typically 30 seconds). That means, `ClientWebSocket` has the Keep-Alive ON by default, with Unsolicited PONG as the default strategy.
121+
122+
If you want to switch to the PING/PONG strategy, overriding <xref:System.Net.WebSockets.ClientWebSocketOptions.KeepAliveTimeout?displayProperty=nameWithType> is enough:
123+
124+
```csharp
125+
var ws = new ClientWebSocket();
126+
ws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(20);
127+
await ws.ConnectAsync(uri, cts.Token);
128+
```
129+
130+
For a basic `WebSocket`, the Keep-Alive is OFF by default. If you want to use the PING/PONG strategy, both <xref:System.Net.WebSockets.WebSocketCreationOptions.KeepAliveInterval?displayProperty=nameWithType> and <xref:System.Net.WebSockets.WebSocketCreationOptions.KeepAliveTimeout?displayProperty=nameWithType> need to be set:
131+
132+
```csharp
133+
var options = new WebSocketCreationOptions()
134+
{
135+
KeepAliveInterval = WebSocket.DefaultKeepAliveInterval,
136+
KeepAliveTimeout = TimeSpan.FromSeconds(20)
137+
};
138+
var ws = WebSocket.CreateFromStream(stream, options);
139+
```
140+
141+
If the Unsolicited PONG strategy is used, PONG frames are used as a unidirectional heartbeat. They sent regularly with `KeepAliveInterval` intervals, regardless whether the remote endpoint is communicating or not.
142+
143+
In case the PING/PONG strategy is active, a PING frame is sent after `KeepAliveInterval` time passed since the _last communication_ from the remote endpoint. Each PING frame contains an integer token to pair with the expected PONG response. If no PONG response arrived after `KeepAliveTimeout` elapsed, the remote endpoint is deemed unresponsive, and the WebSocket connection is automatically aborted.
144+
145+
```csharp
146+
var ws = new ClientWebSocket();
147+
ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(10);
148+
ws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(10);
149+
await ws.ConnectAsync(uri, cts.Token);
150+
151+
// NOTE: There must be an outstanding read at all times to ensure
152+
// incoming PONGs are processed
153+
var result = await _webSocket.ReceiveAsync(buffer, cts.Token);
154+
```
155+
156+
If the timeout elapses, an outstanding `ReceiveAsync` throws an `OperationCanceledException`:
157+
158+
```txt
159+
System.OperationCanceledException: Aborted
160+
---> 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..)
161+
---> System.Net.WebSockets.WebSocketException (0x80004005): The WebSocket didn't receive a Pong frame in response to a Ping frame within the configured KeepAliveTimeout.
162+
at System.Net.WebSockets.ManagedWebSocket.KeepAlivePingHeartBeat()
163+
...
164+
```
165+
166+
### Keep Reading To Process PONGs
167+
168+
> [!NOTE]
169+
> Currently, `WebSocket` ONLY processes incoming frames while there's a `ReceiveAsync` pending.
170+
171+
> [!IMPORTANT]
172+
> If you want to use Keep-Alive Timeout, it's _crucial_ that PONG responses are _promptly processed_. Even if the remote endpoint is alive and properly sends the PONG response, but the `WebSocket` isn't processing the incoming frames, the Keep-Alive mechanism can issue a "false-positive" Abort. This problem can happen if the PONG frame is never picked up from the transport stream before the timeout elapsed.
173+
174+
To avoid tearing up good connections, users are advised to maintain a pending read on all WebSockets that have Keep-Alive Timeout configured.

0 commit comments

Comments
 (0)