199d7eemade read() return the new ReadResult::Timeout for every read timeout and leave the connection open. The compile-time server default (CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND, 300s) is always in effect, so a handler written as `while (ws.read(msg))`, the form the README's Quick Start uses, no longer ended when a peer went quiet: Timeout is non-zero, so the loop ran its body again with the previous message still in `msg`, and the worker the backstop is meant to reclaim was never released. Nothing caught it because every test of the new result set a timeout explicitly and checked the result by value, and the heartbeat tests keep the connection alive with pings. The two timeouts mean different things. One the caller sets through set_read_timeout() is a request for control back, and is reported as Timeout on a still-open connection. The compile-time default is a backstop against a peer that has gone quiet, and elapsing it is now a failure again: read() returns Fail and closes the connection, as it did before199d7ee. WebSocket tracks whether set_read_timeout() was called, and WebSocketClient carries the same flag over to the WebSocket it creates on connect(). Tests use the heartbeat binary, which compiles both defaults down to 3s: a `while (ws.read(msg))` server handler runs its body once and exits when the client falls silent, and a client that never set a timeout gets Fail with the connection closed. The README and cookbook now say which timeout produces Timeout. Claude-Session: https://claude.ai/code/session_01EF5uZ1X2kaHhqJ8VgfjVaQ
3.7 KiB
title, order, status
| title | order | status |
|---|---|---|
| W06. Set Timeouts | 57 | draft |
ws::WebSocketClient has the same three kinds of timeouts as Client, with the same meaning.
| Kind | API | Default |
|---|---|---|
| Connection | set_connection_timeout |
300s |
| Read | set_read_timeout |
none — waits forever (CPPHTTPLIB_WEBSOCKET_CLIENT_READ_TIMEOUT_SECOND) |
| Write | set_write_timeout |
5s |
Basic usage
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
ws.set_connection_timeout(5, 0); // 5 seconds
ws.set_read_timeout(30, 0); // 30 seconds
ws.set_write_timeout(10, 0); // 10 seconds
if (ws.connect()) {
ws.send("hello");
}
Set the connection and write timeouts before calling connect(). The read timeout can be changed at any time — setting it on an open connection takes effect on the next read().
Use std::chrono
Just like Client, there's an overload that takes a std::chrono duration directly.
using namespace std::chrono_literals;
ws.set_connection_timeout(5s);
ws.set_read_timeout(30s);
ws.set_write_timeout(10s);
What the read timeout means
set_read_timeout() applies to a single read() call. If no message arrives within that time, read() returns ReadResult::Timeout: the connection is still open and nothing was consumed, so you can send on it and read again. That is what separates it from ReadResult::Fail, which means the connection is gone.
This is what lets one thread own a connection in both directions:
using namespace std::chrono_literals;
ws.set_read_timeout(100ms);
std::string msg;
while (ws.is_open()) {
auto r = ws.read(msg);
if (r == httplib::ws::Timeout) {
flush_outgoing(ws); // nothing arrived — send whatever is queued
continue;
}
if (r == httplib::ws::Fail) { break; }
handle(msg);
}
Without a read timeout, read() blocks until a message arrives, so the thread holding the connection never gets to its writes.
Two things to know about Timeout:
- It leaves
msguntouched, and it is non-zero. Sowhile (ws.read(msg))is not usable once a read timeout is set — the loop would keep running with the previous message still inmsg. - It is only reported on a message boundary. If the timeout elapses partway through a fragmented message, that message cannot be resumed and
read()returnsFail.
For connections where long idle periods are normal — waiting on notifications, for example — either leave the read timeout unset, or treat Timeout as the no-op it is and keep looping.
On the server side
A handler's ws::WebSocket has set_read_timeout() too, and the pattern above is how a handler relays between connections instead of parking in read().
The server default is 300s (CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND) rather than "forever": it is a backstop that reclaims a worker from a peer that has gone silent, since a WebSocket handler holds its worker for the life of the connection.
Because it is a backstop rather than something the handler asked for, it does not surface as Timeout. When it elapses, read() returns Fail and closes the connection, so a handler written as while (ws.read(msg)) ends the way it always has. Only a timeout the handler set itself with set_read_timeout() comes back as Timeout.
Unresponsive-peer detection via Ping/Pong is a separate mechanism. See W02. Set a WebSocket Heartbeat for details.
How this differs from Client
For Client's timeout configuration, see C12. Set Timeouts. The behavior and API are nearly identical, but WebSocketClient has no equivalent to set_max_timeout() for capping the whole request — once connected, the connection stays open for as long as you keep calling read().