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;
Flexでreentrantなlexerを作ってみた
作ってみた。
https://github.com/coiled-coil/flex-example/blob/master/lexer.l
%{ #include <string.h> %} %option noyywrap reentrant stack nounput %x MODE_A MODE_B %{ enum { TOK_1 = 10, TOK_2 = 20, TOK_3 = 30, }; %} %% . printf("INITIAL: %s\n", yytext); yy_push_state(MODE_A, yyscanner); return TOK_1; <MODE_A>. printf("MODE_A: %s\n", yytext); BEGIN(MODE_B); return TOK_2; <MODE_B>. printf("MODE_B: %s\n", yytext); yy_pop_state(yyscanner); return TOK_3; %% int main() { const char buf[] = "123456789"; int len = strlen(buf); yyscan_t scanner; yylex_init(&scanner); YY_BUFFER_STATE bs = yy_scan_bytes(buf, len, scanner); int token_id; while ((token_id = yylex(scanner))) { printf("TOKEN: %d\n", token_id); } yy_delete_buffer(bs, scanner); yylex_destroy(scanner); return 1; }
出力結果
====== BEGIN OUTPUT ====== INITIAL: 1 TOKEN: 10 MODE_A: 2 TOKEN: 20 MODE_B: 3 TOKEN: 30 INITIAL: 4 TOKEN: 10 MODE_A: 5 TOKEN: 20 MODE_B: 6 TOKEN: 30 INITIAL: 7 TOKEN: 10 MODE_A: 8 TOKEN: 20 MODE_B: 9 TOKEN: 30 EXIT STATUS: 1 ====== END OUTPUT ======
参考にしたもの。
Start Conditions
http://flex.sourceforge.net/manual/Start-Conditions.html#Start-Conditions
Reentrant
http://flex.sourceforge.net/manual/Reentrant-Example.html#Reentrant-Example
Boost.Build(bjam)で独自拡張子を対応する方法
Bost.Buildの使い方メモです。
よく読めば、公式に書いてあることを簡単に触れているだけですので、
すでにご存知の方はスルーしてください。
コードジェネレーターのような特殊なツールを使いたいときどうすればいいのか。
例として、「*.l」をflexでコンパイルして、「*.c」を生成することを考えてみます。
まず、「*.l」の扱い方を定義したjamファイルを作ります。
flex.jam
import type ; import generators ; type.register L : l ; generators.register-standard flex.flex : L : C ; actions flex { flex -o $(<) $(>) }
actionsの中に実際のコマンドを書きます。
他のとこは大体こぴぺと一部修正でいけることがおおいでしょう。
すこし、補足します。
type.register L : l ;
ここで、拡張子「*.l」に対して、タイプの名前Lを付けています。
generators.register-standard flex.flex : L : C ;
この行に出てくる「C」というのは、bjam本体の方で定義されています。
上で定義した、LというタイプからCというタイプが生成されるというルールを記述しています。この書き方は、1:1の場合の書き方です。
メインのプロジェクト側で、
import flex ;
と書けば使えるようになります。
autoconfを使って単体テストにリモートのサーバー名など環境依存な情報を埋め込む
どうもです。
お久しぶりです。
サーバーと通信する単体テストを書いてるときに、
リモートのサーバー名をどうやって埋め込むか悩んだことはありませんか?
共通ファイルを作ってインクルードするのが一般的だと思いますが、
共通処理が複雑になってテスト自体にバグが出たら本末転倒。
そんなときの解決方法のひとつとして、
autoconfという昔からあるツールが便利なので紹介します。
まず、テストのファイルをtests/example.phptとすると、
そのファイルをtests/example.phpt.inとファイル名の後ろに".in"がつく形式にリネームします。
次に環境依存な部分を @HOGE@ という形式に置き換えます。
それからconfigure.ac というファイルを作り、こんな感じに書けばOK。
AC_INIT([example], 0.0) AC_ARG_WITH( [host], [AS_HELP_STRING([--with-host=HOST], [target host to connect in tests])], [host="$withval"], [host=localhost] ) AC_SUBST([HOST], $host) AC_CONFIG_FILES([tests/example.phpt]) AC_OUTPUT
このあとは、
autoconf
とすれば、configureスクリプトが自動生成されますので、
./configure
とすれば実行できます。
./configure --help
とすればヘルプも見えます。
2回目からは、autoconfのかわりに、autoreconfも使えます。
難しそうだけど、意外と簡単なのです。
動くソースをこちらにあげておきます。
気付いたらだいぶ放置してた・・・
これは時々更新しよう。
MacPorts版gcc 4.5でBoost 1.46 (c++0xモード)をビルドしてみた
まずは、user_config.jamを編集した。
こんな行を追加:
using darwin : 4.5 : g++-mp-4.5 :--std=c++0x ;
次に、tools/build/v2/tools/darwin.jamを編集した。編集箇所は、http://d.hatena.ne.jp/uskz/20100829/p2を参考に-no-cpp-precompを削除した。
# Misc options. flags darwin.compile OPTIONS : -gdwarf-2 ;
toolset=darwin-4.5 を指定してビルド。
./bjam toolset=darwin-4.5 variant=debug stage
はまりポイントはbjamのバージョンは3.1.17でないとエラーになることくらいか。