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.
3.4 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.
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().