Files
cpp-httplib/docs-src/pages/ja/cookbook/w04-websocket-binary.md
T
yhirose 199d7ee248 Tell a WebSocket read timeout apart from a closed connection
read() collapsed every failure into Fail and marked the connection closed with
it, so a read timeout could not be used to get control back and send on the
same connection -- it killed the connection instead. The information was
already there and thrown away: SocketStream::read records Error::Timeout, and
read_websocket_frame flattened it into a bool.

ReadResult gains Timeout, reported only when the timeout elapsed on a frame
boundary with nothing consumed, which is the only case where the stream can be
read again. Every multi-byte field now loops until it has its bytes, which also
fixes a frame header straddling the read buffer's boundary failing the frame:
Stream::read is allowed to return less than asked for, and only the payload
was reading in a loop.

ws::WebSocket::set_read_timeout() lets a server handler bound its own reads,
and WebSocketClient::set_read_timeout() now reaches an already-open connection
instead of only seeding the next connect().

The read timeout macro splits in two. A client waits forever by default -- a
read timeout is the caller's tool for taking back control, not a liveness
check, which is ping/pong's job -- while a server keeps the 300s that reclaims
a worker from a peer gone quiet. Defining the old name still sets both.

Also record a reason on the two WebSocketSSLStream::read failure paths that
returned -1 without one, so get_error() cannot report a previous call's
timeout, and make SocketStream's read timeout atomic now that it can be
changed while a read is in flight.
2026-09-03 14:32:13 -04:00

3.8 KiB

title, order, status
title order status
W04. バイナリフレームを送受信する 55 draft

WebSocketにはテキストフレームとバイナリフレームの2種類があります。JSONやプレーンテキストならテキスト、画像や独自プロトコルの生データならバイナリ、という使い分けです。cpp-httplibのsend()は、オーバーロードで両者を自動的に切り替えます。

送り分けの仕組み

ws.send(std::string("Hello"));           // テキスト
ws.send("Hello", 5);                      // バイナリ
ws.send(binary_data, binary_data_size);   // バイナリ

std::stringを受け取るオーバーロードはテキスト、const char*とサイズを受け取るオーバーロードはバイナリです。ちょっと紛らわしいですが、覚えてしまえば直感的です。

文字列をバイナリとして送りたい場合は、.data()と.size()を明示的に渡します。

std::string raw = build_binary_payload();
ws.send(raw.data(), raw.size()); // バイナリフレーム

受信時の判別

ws.read()の返り値で、受信したフレームがテキストかバイナリかを判別できます。

std::string msg;
auto result = ws.read(msg);

switch (result) {
  case httplib::ws::ReadResult::Text:
    std::cout << "text: " << msg << std::endl;
    break;
  case httplib::ws::ReadResult::Binary:
    std::cout << "binary: " << msg.size() << " bytes" << std::endl;
    handle_binary(msg.data(), msg.size());
    break;
  case httplib::ws::ReadResult::Fail:
    // エラーまたは切断
    break;
  case httplib::ws::ReadResult::Timeout:
    // 読み取りタイムアウト。接続は開いたまま
    break;
}

バイナリフレームもstd::stringに入って渡されますが、中身はバイト列なので注意してください。msg.data()とmsg.size()で生のバイトとして扱えます。

バイナリを使うべき場面

  • 画像・動画・音声: Base64でエンコードせずにそのまま送れるので、オーバーヘッドがない
  • 独自プロトコル: protobufやMessagePackなどの構造化バイナリフォーマット
  • ゲームのネットワーク通信: 低レイテンシが求められる場合
  • センサーデータのストリーミング: 数値列をそのまま送る

Pingもバイナリフレームの一種

WebSocketのPing/PongフレームもOpcodeレベルではバイナリに近い扱いですが、cpp-httplibが自動で処理するので、アプリケーションコードで意識する必要はありません。W02. ハートビートを設定するを参照してください。

サンプル: 画像を送る

// サーバー側: 画像を送りつける
svr.WebSocket("/image", [](const auto &req, auto &ws) {
  auto img = read_image_file("logo.png");
  ws.send(img.data(), img.size());
});
// クライアント側: 受け取ってファイルに保存
httplib::ws::WebSocketClient cli("ws://localhost:8080/image");
cli.connect();

std::string buf;
if (cli.read(buf) == httplib::ws::ReadResult::Binary) {
  std::ofstream ofs("received.png", std::ios::binary);
  ofs.write(buf.data(), buf.size());
}

テキストとバイナリを混ぜて送ることもできます。たとえば「制御メッセージはJSON、データ本体はバイナリ」といったプロトコルを組み立てると、メタデータと生データを効率よく扱えます。

Note: WebSocketのフレームサイズには上限がないわけではありません。巨大なデータを送るときは、アプリケーション側で分割して送るのが安全です。cpp-httplibのデフォルトでは大きなフレームもそのまま処理されますが、メモリを一気に使う点は変わりません。