Boost.Asio + Boost.Spirit の組み合わせTips

どうも、ひさしぶりです。

Boost.Asioでデータを受信して、Boost.Spiritでデータを取り出すためのTipsをちょこちょこ書きます。

はじめに

Boost.Asioは、Boost C++ Libraryの中の非同期な通信や入出力を担当するライブラリーです。
Boost.Spiritはテキストを解析するライブラリーです。
このあたりは初見の方はぐぐって調べてみてください。
以下は、どういうものかは知っている前提になっております。

扱いやすいまとまりで受信する

ネットワークから何かを受信する場合、内部的にはパケットという単位でデータが来ます。
パケットの切れ目は、データを取り出すときの意味のある切れ目とは一致しておらず使いにくいです。


そこで、まずはBoost.Asioで意味のある単位までまとまったデータを受信するというのをやります。

まずは、少なくとも区切り文字を含むように受信する場合です。

boost::asio::async_read_until(socket, response, "\r\n", handler);

あるいは、少なくともあるサイズ以上を受信する場合です。

boost::asio::async_read(socket, response, boost::asio::transfer_at_least(content_length), handler);

この二種類を使い分ければ、どんなプロトコルでも意味のあるまとまりごとに受信できるようになります。

データを解析して取り出す

次に、受信したデータの取り出し方です。

まずは、ライブラリー付属のサンプルに載っているstd::istreamを介する方法です。

std::istream response_stream(&response);
std::string line;
while (getline(response_stream, line)) {
  // ...
}

これでいいときもあるのでしょうが、いちいちストリームを作らないといけないところと、
ストリームに対する操作があんまり便利ではないということで、もっと直接的な方法を紹介します。

実は、こういう方法でstream_bufはconst char *型に変換できます。

const char *iter = boost::asio::buffer_cast<const char *>(response.data());

利用できるサイズは、response.size()で取れますが、これはasync_readに指定したcontent_sizeよりも大きくなるので、async_readに指定した値を覚えておいて、それを使うことが多いでしょう。

const char *型がとれたら、これを直接Boost.Spiritを使ってほしい値を取り出すことができます。

const char *iter = boost::asio::buffer_cast<const char *>(response.data());
const char *last = strstr(iter, "\r\n");
unsigned int consume_size = last - iter + 2;
unsigned int chunk_size = 0;
qi::parse(iter, last, qi::hex, chunk_size);

データの解析が終わったら、次の読み取りのまえにデータの読みとり位置を進めておくのが必要です。

response.consume(consume_size);

こんな感じのノリでどんなデータでも解析できると思います。

おわりに

いかがでしたでしょうか。

Boost.AsioとBoost.Spiritの組み合わせは便利ですね。



なお、上記の例では、次のようなヘッダーがインクルードされているものとして話を進めました。

#include <string>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/spirit/include/qi.hpp>

using namespace std;
using boost::asio::ip::tcp;
namespace qi = boost::spirit::qi;