Files
cpp-httplib/docs-src/pages/ja/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.8 KiB
Raw Blame History

title, order, status
title order status
W01. WebSocketエコーサーバー/クライアントを実装する 52 draft

WebSocketは、クライアントとサーバーの間で双方向にメッセージをやり取りするためのプロトコルです。cpp-httplibはサーバーとクライアントの両方のAPIを提供しています。まずは一番シンプルなエコーサーバーから見てみましょう。

サーバー: エコーサーバー

#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); // 受け取った内容をそのまま返す
    }
  });

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

svr.WebSocket()でWebSocket用のハンドラを登録します。ハンドラが呼ばれた時点で、すでにWebSocketのハンドシェイクは完了しています。ループの中でws.read()してws.send()するだけで、エコー動作が完成します。

read()の返り値はReadResult列挙値で、次の4種類です。

  • ReadResult::Text: テキストメッセージを受信
  • ReadResult::Binary: バイナリメッセージを受信
  • ReadResult::Fail: エラー、または接続が閉じた
  • ReadResult::Timeout: set_read_timeout()で自分が設定した読み取りタイムアウトが、何も受信しないまま経過した。接続は開いたまま。コンパイル時のデフォルトのタイムアウトは接続を閉じ、Failとして返る(W06. タイムアウトを設定するを参照)

クライアント: エコーを叩く

#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();
}

URLにはws://(平文)またはwss://(TLS)を指定します。connect()でハンドシェイクを行い、あとはsend()とread()でサーバーと同じAPIでやり取りできます。

テキストとバイナリの送り分け

send()には2つのオーバーロードがあり、テキストとバイナリで使い分けられます。

ws.send("Hello");                        // テキストフレーム
ws.send(binary_data, binary_data_size);  // バイナリフレーム

std::stringを受け取るオーバーロードはテキスト、const char*とサイズを受け取るオーバーロードはバイナリとして送られます。詳しくはW04. バイナリフレームを送受信するを参照してください。

スレッドとの関係

WebSocket接続はハンドラが終わるまで生き続けるので、1接続につきワーカースレッドを1つ占有します。同時接続数が多い場合は、スレッドプールを動的スケーリングに設定しましょう。

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

詳細はS21. マルチスレッド数を設定するを参照してください。

Note: HTTPSサーバーの上でWebSocketを動かしたいときは、httplib::Serverの代わりにhttplib::SSLServerを使えば、同じWebSocket()ハンドラがそのまま動きます。クライアント側はwss://スキームを指定するだけです。CA証明書やクライアント証明書の設定はW05. wss接続でTLSを設定するを参照してください。