はじめに
boost.asioでデータを取得する際に
strambufferを使って取得する事があります
取得したデータをstringに保存したい時ありますよね
(例えば jsonをデコードする等)
そんな時に役立つTips
全体像
全体のコードです
とりあえず http GETし、contents取得します
下記のasioサンプルコードをベースに。
asio sample sync
同期
まず ストリームをそのまま 標準出力へ表示
なんともない処理ですね
上記のコードの一部を抜粋
// 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のサンプルでは上記のように istreamを使って stringに流しています
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
上記の方法。streambuf.data()でmutable_buffer or const_buffer を取得し
buffer_cast を使う
この方法で、streamを使わずにstringにデータを流し込めます。
ただし、私が試したところ、ゴミデータまで取得されたので
substrで、正常データまでで切りました
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 を使えば
バッファ全てを取得できますね
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を使うと 非常に簡単になります
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 便利!