Files
cpp-httplib/docs-src/pages/ja/cookbook/w06-websocket-timeouts.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

4.6 KiB
Raw Blame History

title, order, status
title order status
W06. タイムアウトを設定する 57 draft

ws::WebSocketClientにはClientと同じ3種類のタイムアウトがあり、意味も同じです。

種類 API デフォルト
接続タイムアウト set_connection_timeout 300秒
読み取りタイムアウト set_read_timeout なし。無期限に待つ(CPPHTTPLIB_WEBSOCKET_CLIENT_READ_TIMEOUT_SECOND)
書き込みタイムアウト set_write_timeout 5秒

基本の使い方

httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");

ws.set_connection_timeout(5, 0);  // 5秒
ws.set_read_timeout(30, 0);       // 30秒
ws.set_write_timeout(10, 0);      // 10秒

if (ws.connect()) {
  ws.send("hello");
}

接続タイムアウトと書き込みタイムアウトはconnect()を呼ぶ前に設定してください。読み取りタイムアウトはいつでも変更でき、接続済みの状態で設定した場合は次のread()から効きます。

std::chronoで指定する

Clientと同じく、std::chronoの期間を直接渡すオーバーロードもあります。

using namespace std::chrono_literals;

ws.set_connection_timeout(5s);
ws.set_read_timeout(30s);
ws.set_write_timeout(10s);

読み取りタイムアウトの意味

set_read_timeout()は「1回のread()呼び出し」に対するタイムアウトです。メッセージが届かないまま指定時間が経過するとread()はReadResult::Timeoutを返します。このとき接続は開いたままで、1バイトも読み進めていないので、そのまま送信して読み直せます。接続が失われたことを意味するReadResult::Failとはここが違います。

1本の接続を1つのスレッドで双方向に扱えるのはこのためです。

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);  // 何も届いていない。溜まっている分を送る
    continue;
  }
  if (r == httplib::ws::Fail) { break; }
  handle(msg);
}

読み取りタイムアウトを設定しないとread()はメッセージが届くまで戻らないので、接続を持っているスレッドは送信に手が回りません。

Timeoutについて2点あります。

  • msgは書き換えられません。値も0以外なので、読み取りタイムアウトを設定した状態でwhile (ws.read(msg))と書くと、前回のメッセージがmsgに残ったままループが回り続けます。
  • 報告されるのはメッセージの境界だけです。分割されたメッセージの途中でタイムアウトした場合、そのメッセージは再開できないのでread()はFailを返します。

通知の待受のように長時間メッセージが来ないことが正常な接続では、読み取りタイムアウトを設定しないままにするか、Timeoutを「まだ何も来ていない」印として扱ってループを続けてください。

サーバ側

ハンドラが受け取るws::WebSocketにもset_read_timeout()があります。ハンドラがread()で止まったままにならないので、上と同じ書き方で複数の接続の間をメッセージが中継できます。

サーバ側のデフォルトは「無期限」ではなく300秒(CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND)です。WebSocketのハンドラは接続が続く限りワーカーを1つ占有するので、無言になったピアからワーカーを回収する保険として働きます。

この保険はハンドラが求めたタイムアウトではないので、Timeoutとしては返りません。経過するとread()はFailを返して接続を閉じるため、while (ws.read(msg))と書いたハンドラは従来どおりそこで終わります。Timeoutが返るのは、ハンドラ自身がset_read_timeout()で設定したタイムアウトだけです。

Ping/Pongによる無応答ピア検出は別の仕組みです。詳しくはW02. ハートビートを設定するを参照してください。

Clientとの違い

Clientのタイムアウト設定についてはC12. タイムアウトを設定するを参照してください。挙動とAPIはほぼ同じですが、WebSocketClientにはset_max_timeout()に相当するリクエスト全体のタイムアウトはありません。接続を確立したあとは、read()のループを回し続ける限り接続が維持されます。