Files
cpp-httplib/docs-src/pages/en/cookbook/w01-websocket-echo.md
T
yhirose 6b8c3f5387 Report only a caller-set WebSocket read timeout as Timeout
199d7ee made 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
before 199d7ee. 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
2026-09-11 17:29:20 -04:00

3.0 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: a read timeout you set with set_read_timeout() elapsed with nothing received; the connection is still open. The compile-time default timeout closes the connection and is reported as Fail instead — 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.