mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-08 12:23:47 +00:00
read() collapsed every failure into Fail and marked the connection closed with it, so a read timeout could not be used to get control back and send on the same connection -- it killed the connection instead. The information was already there and thrown away: SocketStream::read records Error::Timeout, and read_websocket_frame flattened it into a bool. ReadResult gains Timeout, reported only when the timeout elapsed on a frame boundary with nothing consumed, which is the only case where the stream can be read again. Every multi-byte field now loops until it has its bytes, which also fixes a frame header straddling the read buffer's boundary failing the frame: Stream::read is allowed to return less than asked for, and only the payload was reading in a loop. ws::WebSocket::set_read_timeout() lets a server handler bound its own reads, and WebSocketClient::set_read_timeout() now reaches an already-open connection instead of only seeding the next connect(). The read timeout macro splits in two. A client waits forever by default -- a read timeout is the caller's tool for taking back control, not a liveness check, which is ping/pong's job -- while a server keeps the 300s that reclaims a worker from a peer gone quiet. Defining the old name still sets both. Also record a reason on the two WebSocketSSLStream::read failure paths that returned -1 without one, so get_error() cannot report a previous call's timeout, and make SocketStream's read timeout atomic now that it can be changed while a read is in flight.
81 lines
3.5 KiB
Markdown
81 lines
3.5 KiB
Markdown
---
|
||
title: "W02. Set a WebSocket Heartbeat"
|
||
order: 53
|
||
status: "draft"
|
||
---
|
||
|
||
WebSocket connections stay open for a long time, and proxies or load balancers will sometimes drop them for being "idle." To prevent that, you periodically send Ping frames to keep the connection alive. cpp-httplib can do this for you automatically.
|
||
|
||
## Server side
|
||
|
||
```cpp
|
||
svr.set_websocket_ping_interval(30); // ping every 30 seconds
|
||
|
||
svr.WebSocket("/chat", [](const auto &req, auto &ws) {
|
||
// ...
|
||
});
|
||
```
|
||
|
||
Just pass the interval in seconds. Every WebSocket connection this server accepts will be pinged on that interval.
|
||
|
||
There's a `std::chrono` overload too.
|
||
|
||
```cpp
|
||
using namespace std::chrono_literals;
|
||
svr.set_websocket_ping_interval(30s);
|
||
```
|
||
|
||
## Client side
|
||
|
||
The client has the same API.
|
||
|
||
```cpp
|
||
httplib::ws::WebSocketClient cli("ws://localhost:8080/chat");
|
||
cli.set_websocket_ping_interval(30);
|
||
cli.connect();
|
||
```
|
||
|
||
Call it before `connect()`.
|
||
|
||
## The default
|
||
|
||
The default interval is set by the build-time macro `CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND`. Usually you won't need to change it, but adjust downward if you're dealing with an aggressive proxy.
|
||
|
||
## What about Pong?
|
||
|
||
The WebSocket protocol requires that Ping frames are answered with Pong frames. cpp-httplib responds to Pings automatically — you don't need to think about it in application code.
|
||
|
||
## Picking an interval
|
||
|
||
| Environment | Suggested |
|
||
| --- | --- |
|
||
| Normal internet | 30–60s |
|
||
| Strict proxies (e.g. AWS ALB) | 15–30s |
|
||
| Mobile networks | 60s+ (too short drains battery) |
|
||
|
||
Too short wastes bandwidth; too long and connections get dropped. As a rule of thumb, target about **half the idle timeout** of whatever's between you and the client.
|
||
|
||
> **Warning:** A very short ping interval spawns background work per connection and increases CPU usage. For servers with many connections, keep the interval modest.
|
||
|
||
## Detecting an unresponsive peer
|
||
|
||
Sending pings alone doesn't tell you anything if the peer just silently dies — the TCP socket might still look open while the process on the other end is long gone. To catch that, enable the max-missed-pongs check: if N consecutive pings go unanswered, the connection is closed.
|
||
|
||
```cpp
|
||
cli.set_websocket_max_missed_pongs(2); // close after 2 consecutive unacked pings
|
||
```
|
||
|
||
The server side has the same `set_websocket_max_missed_pongs()`.
|
||
|
||
With a 30-second ping interval and `max_missed_pongs = 2`, a dead peer is detected within roughly 60 seconds and the connection is closed with `CloseStatus::GoingAway` and the reason `"pong timeout"`.
|
||
|
||
The counter is reset whenever `read()` consumes an incoming Pong frame, so this only works if your code is actively calling `read()` in a loop — which is what a normal WebSocket client does anyway.
|
||
|
||
### Why the default is 0
|
||
|
||
`max_missed_pongs` defaults to `0`, which means "never close the connection because of missing pongs." Pings are still sent on the heartbeat interval, but their responses aren't checked. If you want unresponsive-peer detection, set it explicitly to `1` or higher.
|
||
|
||
On the server side, even with `0`, a dead connection won't linger forever: while a handler is inside `read()`, `CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND` (default **300 seconds = 5 minutes**) acts as a backstop. A client has no backstop of its own — it waits forever unless you set a read timeout — so there `max_missed_pongs` is what notices an unresponsive peer at all. On either side, it is also how you notice one **faster** than that 5-minute fallback.
|
||
|
||
> For handling a closed connection, see [W03. Handle connection close](../w03-websocket-close).
|