W06. タイムアウトを設定する
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"); }
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);
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); }
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()のループを回し続ける限り接続が維持されます。