あめだまふぁくとりー

Boost.Graphとかできますん

Boost.Asio で read / async_read を使用する際の注意点

1. boost::asio::streambuf と組み合わせる場合

うっかり,

boost::asio::async_read(socket, streambuf, handler);

見たいに書くと, 永遠に handler が起動されない可能性があるので注意しましょう. これは, async_read は指定されたサイズ分読みを行い, デフォルトでは 65535 になっているからです. 少量のデータを送受信する場合は, boost::asio::transfer_at_least 等と一緒に使用しましょう.

// 少なくとも 4 byte 読むが, 一度の read でそれ以上読めたら handler が起動される
boost::asio::async_read(socket, streambuf, boost::asio::transfer_at_least(4), handler);

2. coroutine を使用かつ socket が close された場合

ドキュメンにも記載されていますが, read() / async_read() は, 期待したサイズ分を読む前に socket の終端 (EOF) を読むと, boost::asio::error::eof をエラーコードに指定して終了してしまいます. そのため, 以下のコードのように try-catch でエラーハンドリングすると, 何バイト読んだのかが分からず, 読み込んだデータを処理できない場合があります. 最悪, 読み込んだデータが捨てられてしまいます.

try {
    auto const size = boost::asio::async_read(socket, boost::asio::buffer(buf), yield);
}
catch (boost::system::system_error& e) {
    if (e.code() == boost::asio::error::eof) {
        // 何バイト読めたのか分からない
    }
}

以上を考慮すると, error_code を指定してあげるのが良いと思われます.

auto ec = boost::system::error_code{};
auto const size = boost::asio::async_read(socket, boost::asio::buffer(buf), yield[ec]);
if (ec && ec != boost::asio::error::eof) {
   throw boost::system::system_error{ec};
}

// 何か有益な処理
// ...

if (ec && ec == boost::asio::error::eof) {
    socket.close();
    return;
}