Files
cpp-httplib/docs-src/pages/en/cookbook/w01-websocket-echo.md
yhirose 199d7ee248 Tell a WebSocket read timeout apart from a closed connection
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.
2026-09-03 14:32:13 -04:00

2.9 KiB

title, order, status
title order status
W01. Implement a WebSocket Echo Server and Client 52 draft

WebSocket is a protocol for two-way messaging between client and server. cpp-httplib provides APIs for both sides. Let's start with the simplest example: an echo server.

Server: echo server

#include <httplib.h>

int main() {
  httplib::Server svr;

  svr.WebSocket("/echo", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
    std::string msg;
    while (ws.is_open()) {
      auto result = ws.read(msg);
      if (result == httplib::ws::ReadResult::Fail) {
        break;
      }
      ws.send(msg); // echo back what we received
    }
  });

  svr.listen("0.0.0.0", 8080);
}

Register a WebSocket handler with svr.WebSocket(). By the time the handler runs, the WebSocket handshake is already complete. Inside the loop, just ws.read() and ws.send() to get a working echo.

The read() return value is a ReadResult enum:

  • ReadResult::Text: received a text message
  • ReadResult::Binary: received a binary message
  • ReadResult::Fail: error, or connection closed
  • ReadResult::Timeout: the read timeout elapsed with nothing received; the connection is still open. Only appears once a read timeout is set — see W06. Set Timeouts

Client: talk to the echo server

#include <httplib.h>

int main() {
  httplib::ws::WebSocketClient cli("ws://localhost:8080/echo");
  if (!cli.connect()) {
    std::cerr << "failed to connect" << std::endl;
    return 1;
  }

  cli.send("Hello, WebSocket!");

  std::string msg;
  if (cli.read(msg) != httplib::ws::ReadResult::Fail) {
    std::cout << "received: " << msg << std::endl;
  }

  cli.close();
}

Use a ws:// (plain) or wss:// (TLS) URL. Call connect() to do the handshake, then send() and read() work the same as on the server side.

Text vs. binary

send() has two overloads that let you choose the frame type.

ws.send("Hello");                        // text frame
ws.send(binary_data, binary_data_size);  // binary frame

The std::string overload sends as text; the const char* + size overload sends as binary. A bit subtle, but once you know it, it's intuitive. See W04. Send and receive binary frames for details.

Thread pool implications

A WebSocket handler holds its worker thread for the entire life of the connection — one connection per thread. For many concurrent clients, configure a dynamic thread pool.

svr.new_task_queue = [] {
  return new httplib::ThreadPool(8, 128);
};

See S21. Configure the thread pool.

Note: To run WebSocket over HTTPS, use httplib::SSLServer instead of httplib::Server — the same WebSocket() handler just works. On the client side, use a wss:// URL. For CA and client certificate configuration, see W05. Configure TLS for wss:// Connections.