Boost.Asio バッファの消費

Boost.Asioで送受信/読み書き、とくに受信/読み込みを行う場合には、「バッファの消費」という考え方に気をつける必要があります。


以下のようなケースを考えてみます。
これは延々と受信を繰り返すだけの単純なプログラムです。

#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

namespace asio = boost::asio;
using asio::ip::tcp;

class Client {
    asio::io_service& io_service_;
    tcp::socket socket_;

    asio::streambuf receive_buffer_;
public:
    Client(asio::io_service& io_service)
        : io_service_(io_service),
          socket_(io_service) {}

    void connect()
    {
        socket_.connect(tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 31400));
    }

    void receive()
    {
        asio::async_read(
            socket_,
            receive_buffer_,
            asio::transfer_at_least(1), // 少なくても1バイト受信する
            boost::bind(&Client::on_receive, this, asio::placeholders::error));
    }

private:
    void on_receive(const boost::system::error_code& error)
    {
        // 受信したデータをstringに変換
        const std::string data(asio::buffer_cast<const char*>(receive_buffer_.data()), receive_buffer_.size());

        std::cout << data << std::endl;

        // 続けて受信する
        receive();
    }
};

int main()
{
    asio::io_service io_service;
    Client client(io_service);

    client.connect();
    client.receive();

    io_service.run();
}

ここで、最初の受信で"abc"を受信した場合、on_receive()関数のdata変数には"abc"文字列が入ります。
on_receive()関数の中で続けて受信処理を行い、"def"が受信された場合、on_receive()関数のdata変数には"abcdef"文字列が入ります。
前回の受信データがそのまま残ってしまっています。


受信したバッファのうち、処理を行ったものは消費しなければなりません。
バッファを消費する方法は大きく2つあります:

  • バッファをstd::istreamに結びつけ、ストリームで読み込み処理を行う。

読み込まれたバッファは消費されます。

void on_receive(const boost::system::error_code& error)
{
    std::istream is(&receive_buffer_);

    std::string data;
    is >> data; // ストリームから読み込む:読み込んだ分が消費される

    receive();
}
  • asio::streambuf::consume()関数を使用して先頭Nバイトを消費する

consume()は、指定したバイト数分のバッファを消費します。
これは主に、バッファの全てを一気に読み込み、バッファをクリアするために使用します。

void on_receive(const boost::system::error_code& error)
{
    const std::string data(asio::buffer_cast<const char*>(receive_buffer_.data()), receive_buffer_.size());
    receive_buffer_.consume(receive_buffer_.size()); // 受信バッファのサイズ分消費する

    receive();
}


バッファの消費という考え方は、受信関数の「少なくても」Nバイト、「少なくても」指定した文字まで受信するという仕様から、「まだ受信してない残りを受信する」というユーザーの設計を許可するためにあるのだと思いますが、この設計意図を把握してないとなかなか難しいかもしれません。


参照:
boost::asio::basic_streambuf::consume