Samples that did not compile or run as shown: - res.user_data.get<T>() inside a generic lambda needs the `template` keyword; use explicit parameter types (tour 09, cookbook s15). - listen() on a Unix domain socket fails with port 0 (tour 09, s22). - "*.dev.local" is not a NO_PROXY pattern (c16). - ssl_backend_error() holds a verify result, not an ERR_get_error() value, after a verification failure; decode each with the matching OpenSSL function (c18). - The content provider's `length` is everything that remains, so the sample read the whole file in one call (s05). Statements corrected: - Client keep-alive is off by default; c14 is rewritten around set_keep_alive(true). - Mounted files are looked up before GET handlers (tour 04, s04). - Params keep insertion order, and to_string(Error::Connection) reads "Could not establish connection" (tour 02). - A chunked provider ends with sink.done(), and post_routing_handler runs before the response is sent (tour 09). - Timeouts surface as Error::Read; Error::Timeout comes from the stream API (c17). The max timeout cuts off the wait for the response only (c13). The progress callback needs Content-Length (c11). - Encoding selection follows q-values, then Brotli, gzip, Zstd (s08), and the client compresses with the first of those it was built with (c15). - stop() cuts a provider-driven response short (s19); a rejected content_reader already gets 400 or 413 (s07); user_data values must be copyable (s12); Client accepts a client certificate too (t04); on_message() is the fallback for every unhandled event and 204/403/404 end reconnection (e04); the pong timeout takes two to three intervals and ends a waiting read() (w02). In the LLM app tutorial, an uncaught exception does not crash the server, so say what it does instead. Drop the server and client timeout settings whose stated purpose, covering inference and download time, they do not serve: those timeouts bound a single socket wait. Update the llama.cpp server layout in chapter 7.
4.8 KiB
title, order, status
| title | order | status |
|---|---|---|
| W02. ハートビートを設定する | 53 | draft |
WebSocket接続は長時間つなぎっぱなしになるので、プロキシやロードバランサが「アイドルだから」と勝手に切ってしまうことがあります。これを防ぐために、定期的にPingフレームを送って接続を生かしておく仕組みがあります。cpp-httplibでは、指定した間隔で自動的にPingを送ってくれます。
サーバー側の設定
svr.set_websocket_ping_interval(30); // 30秒ごとにPing
svr.WebSocket("/chat", [](const auto &req, auto &ws) {
// ...
});
set_websocket_ping_interval()に秒数を渡すだけです。このサーバーが受け入れるすべてのWebSocket接続に対して、指定した間隔でPingが送られます。
std::chronoの期間を受け取るオーバーロードもあります。
using namespace std::chrono_literals;
svr.set_websocket_ping_interval(30s);
クライアント側の設定
クライアント側でも同じAPIがあります。
httplib::ws::WebSocketClient cli("ws://localhost:8080/chat");
cli.set_websocket_ping_interval(30);
cli.connect();
connect()を呼ぶ前に設定しておきましょう。
デフォルト値
デフォルトのPing間隔は、ビルド時のマクロCPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECONDで決まります。通常はそのままで問題ありませんが、特別なプロキシ環境に合わせて短くしたい場合は調整してください。
PongはどうやってpIngに応答するか
WebSocketプロトコルでは、PingフレームにはPongフレームで応答することが決まっています。cpp-httplibは受信したPingに自動でPongを返すので、アプリケーションコード側で気にする必要はありません。
Pingの間隔をどう決めるか
| 環境 | 推奨 |
|---|---|
| 通常のインターネット接続 | 30〜60秒 |
| 厳しいプロキシ(AWS ALBなど) | 15〜30秒 |
| モバイル回線 | 短すぎるとバッテリーを食う、60秒以上 |
短すぎると無駄なトラフィックになり、長すぎると接続が切れます。だいたい接続が切れる時間の半分くらいが目安です。
Warning: Ping間隔を極端に短くすると、WebSocket接続ごとにバックグラウンドでスレッドが走るので、CPU負荷が上がります。接続数が多いサーバーでは控えめな値に設定しましょう。
無応答のピアを検出する
Pingを送るだけでは、相手が「黙って落ちた」場合に気付けません。TCPの接続自体は生きているように見えるのに、相手のプロセスはもう応答しない、というケースです。これを検出するには、送ったPingに対してPongがN回連続で返ってこなかったら接続を切る、というオプションを有効にします。
cli.set_websocket_max_missed_pongs(2); // 2回連続でPongが返ってこなければ切断
サーバー側にも同じset_websocket_max_missed_pongs()があります。
たとえばPing間隔が30秒でmax_missed_pongs = 2なら、無応答のピアは応答が止まってから60〜90秒で検出され、CloseStatus::GoingAway(理由は"pong timeout")で接続が閉じられます。そのときread()で待っていた呼び出しはFailを返します。
この仕組みはread()を呼んでPongフレームを消費したタイミングでカウンタがリセットされます。つまり通常のWebSocketクライアントのようにread()をループで回していれば、特に意識することなく動きます。
デフォルトは無効
max_missed_pongsのデフォルトは0で、これは「Pongが何回返ってこなくてもこの仕組みでは切断しない」という意味です。Ping自体は送られ続けますが、応答の有無はチェックされません。無応答ピアを検出したい場合は明示的に1以上を設定してください。
サーバ側は0のままでも接続が残り続けることはありません。ハンドラがread()を呼んでいる間はCPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND(デフォルト300秒 = 5分)が保険として働きます。一方クライアント側にはこの保険がなく、読み取りタイムアウトを設定しない限り無期限に待つので、無応答ピアを検出する手段はmax_missed_pongsだけです。どちらの側でも「もっと速く検出したい」ときに使うオプションでもあります。
接続が閉じたときの処理はW03. 接続クローズをハンドリングするを参照してください。