LoginSignup
2
4

More than 5 years have passed since last update.

【boost.asio】streambufferからstd::stringへコピー【coroutine】

Posted at

はじめに

boost.asioでデータを取得する際に
strambufferを使って取得する事があります
取得したデータをstringに保存したい時ありますよね
(例えば jsonをデコードする等)
そんな時に役立つTips

全体像

全体のコードです

とりあえず http GETし、contents取得します

下記のasioサンプルコードをベースに。
asio sample sync

同期

まず ストリームをそのまま 標準出力へ表示
なんともない処理ですね
上記のコードの一部を抜粋

sync_stream
// Write whatever content we already have to output.
if (response.size() > 0)
  std::cout << &response;

// Read until EOF, writing data to output as we go.
boost::system::error_code error;
while (boost::asio::read(socket, response,
    boost::asio::transfer_at_least(1), error))
  std::cout << &response;

responseは boost::asio::streambuf型です
streamなので coutに流す事が出来ます。

ではこれを std::string に送る方法です

istreamを使う

boost stream buff

boostのサンプルでは上記のように istreamを使って stringに流しています

istream
boost::asio::streambuf b;

// reserve 512 bytes in output sequence
boost::asio::streambuf::mutable_buffers_type bufs = b.prepare(512);

size_t n = sock.receive(bufs);

// received data is "committed" from output sequence to input sequence
b.commit(n);

std::istream is(&b);
std::string s;
is >> s;

同じように streambufを istream等を使い stringに流し込む事は可能ですが
空白文字が消えるなど、データ次第ではstream由来の問題が発生するので
今回は この方法はNGです

buffer_cast

faithandbrave

上記の方法。streambuf.data()でmutable_buffer or const_buffer を取得し
buffer_cast を使う

この方法で、streamを使わずにstringにデータを流し込めます。
ただし、私が試したところ、ゴミデータまで取得されたので
substrで、正常データまでで切りました

buffer_cast
boost::system::error_code error;
std::string result = "";
while (boost::asio::read(socket, response,
        boost::asio::transfer_at_least(1), error)) {

  std::string tmp = boost::asio::buffer_cast<const char *>(response.data());
  result += tmp.substr(0, response.size());
}

response.size() で実際に取得したデータサイズがわかるので
その長さで切り取りコピーします
result に受信データが全部入ったはず

非同期

asioのサンプルは下記
asio sample async

基本的に connect、read、write関数に async_をつけ、ハンドラを登録して
コールバック駆動にすれば簡単に動きますが

read()は、同期の場合は戻り値がtrueの場合はまだデータが存在していたけど
非同期のため、戻り値は使えません
かわりに、boost::system::error_code を使います

通常はsucceeded=0 ですが、eofやエラー時は !=0 になるので
if(!err) で eofの場合は処理が終了します。

出力すべき文字列を キャプチャして buffer_cast を使えば
バッファ全てを取得できますね

async
  void handle_read_content(const boost::system::error_code& err)
  {
    if (!err)
    {
      // Write all of the data that has been read so far.
      std::cout << &response_;

      // Continue reading remaining data until EOF.
      boost::asio::async_read(socket_, response_,
          boost::asio::transfer_at_least(1),
          boost::bind(&client::handle_read_content, this,
            boost::asio::placeholders::error));
    }
    else if (err != boost::asio::error::eof)
    {
      std::cout << "Error: " << err << "\n";
    }
  }

coroutine

非同期で書くとかなり面倒です。
そこで corouineを使うと 非常に簡単になります

coroutine
boost::system::error_code ec;
std::string result;

while (!ec){
  boost::asio::async_read(socket, response,
    boost::asio::transfer_at_least(1), yield[ec]);

  std::string tmp = boost::asio::buffer_cast<const char *>(response.data());
  result += tmp.substr(0, response.size());
}

coroutine 便利!

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4