c++でmsgpackを使ってみる
msgpackとは
MessagePack: It's like JSON. but fast and small.
MessagePackは、効率の良いバイナリ形式のオブジェクト・シリアライズ フォーマットです。 JSONの置き換えとして使うことができ、様々なプログラミング言語をまたいでデータを交換することが可能です。 しかも、JSONよりも速くてコンパクトです。 例えば、小さな整数値はたった1バイト、短い文字列は文字列自体の長さ+1バイトでシリアライズできます。
らしい
仕事で使う機会があったのでc++でサンプルコードを実行してみる
msgpackのインストール
MessagePack: It's like JSON. but fast and small. → C/C++ msgpack のページに従う
準備
` インストール条件
gcc >= 4.1.0 cmake >= 2.8.0
gccバージョン確認
$ g++ -v Using built-in specs. COLLECT_GCC=g++ COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04.3' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3)
cmake確認
なかったのでインストール
sudo apt-get install cmake $ cmake -version cmake version 2.8.12.2
msgpackのインストール
msgpackのgitはここ
$ git clone https://github.com/msgpack/msgpack-c.git $ cd msgpack-c $ cmake . $ make $ sudo make install
msgpackのバージョンを確認
- 現在のmasterのバージョンは2.1.5らしい
- msgpack-c/CHANGELOG.md at master · msgpack/msgpack-c · GitHub
msgpackのサンプルコードを実行
msgpack-c/QUICKSTART-CPP.md at master · msgpack/msgpack-c · GitHub
First program(単なるデータのシリアライズとデシリアライズ)
コード
以下を hello.cpp という名前で保存
#include <msgpack.hpp> #include <vector> #include <string> #include <iostream> int main(void) { // serializes this object. std::vector<std::string> vec; vec.push_back("Hello"); vec.push_back("MessagePack"); // serialize it into simple buffer. msgpack::sbuffer sbuf; msgpack::pack(sbuf, vec); // deserialize it. msgpack::object_handle oh = msgpack::unpack(sbuf.data(), sbuf.size()); // print the deserialized object. msgpack::object obj = oh.get(); std::cout << obj << std::endl; //=> ["Hello", "MessagePack"] // convert it into statically typed object. std::vector<std::string> rvec; obj.convert(rvec); }
コンパイル
$ g++ -Ipath_to_msgpack/include hello.cpp -o hello 実行 $ ./hello ["Hello", "MessagePack"]
Streaming into an array or map(配列やマップのシリアライズとデシリアライズ)
msgpack-c/QUICKSTART-CPP.md at master · msgpack/msgpack-c · GitHub
ここにはシリアライズはあるが、デシリアライズの処理がないので、自分で作ってみる
$ cat stream.cpp #include <msgpack.hpp> #include <iostream> #include <string> #include <sstream> int main(void) { // serializes multiple objects into one message containing an array using msgpack::packer. msgpack::sbuffer buffer; msgpack::packer<msgpack::sbuffer> pk(&buffer); pk.pack_array(4); pk.pack(std::string("Log message ... 1")); pk.pack(std::string("Log message ... 2")); pk.pack(std::string("Log message ... 3")); pk.pack(std::string("Log message ... 4")); // serializes multiple objects into one message containing a map using msgpack::packer. msgpack::sbuffer buffer2; msgpack::packer<msgpack::sbuffer> pk2(&buffer2); pk2.pack_map(2); pk2.pack(std::string("x")); pk2.pack(3); pk2.pack(std::string("y")); pk2.pack(3.4321); // pk2.pack(4); /*---- 以下自作部分 ----*/ // unpack array msgpack::unpacked msg; msgpack::unpack(msg, buffer.data(), buffer.size()); msgpack::object obj = msg.get(); msgpack::object_array obj_array = obj.via.array; std::string str[4]; for (int i = 0; i < 4; i++) { (obj_array.ptr[i]).convert(str[i]); std::cout << "str[" << i << "]: " << str[i] << std::endl; } // unpack map msgpack::unpacked msg2; msgpack::unpack(msg2, buffer2.data(), buffer2.size()); msgpack::object obj2 = msg2.get(); msgpack::object_map obj_map = obj2.via.map; for (int i = 0; i < 2; i++) { std::string map_key; //std::cout << "key: " << obj_map.ptr[i].key; (obj_map.ptr[i].key).convert(map_key); // 以下、mapのvalの方は、intの場合とfloatの場合があるので一旦floatにconvert // してからstringに変換した float f; // obj_map.ptr[i].valはmsgpackのobject // 一旦floatにconvertしてからそれを文字列に変換した (obj_map.ptr[i].val).convert(f); std::stringstream ss; ss << f; std::cout << "key: " << map_key << " val: " << ss.str() << std::endl; // これを実行するとintは2, floatは4と表示される //std::cout << "type: " << (obj_map.ptr[i].val).type << std::endl; // 以下のようなif文で判定できるが、このプログラムでは一括してfloatに変換している //if ( (obj_map.ptr[i].val).type == msgpack::type::POSITIVE_INTEGER ) { //} } // $ g++ -Ipath_to_msgpack/include stream.cpp -o stream }
実行結果
$ g++ -Ipath_to_msgpack/include stream.cpp -o stream $ ./stream str[0]: Log message ... 1 str[1]: Log message ... 2 str[2]: Log message ... 3 str[3]: Log message ... 4 key: x val: 3 key: y val: 3.4321
上の修正は以下を参考にした
msgpackのオブジェクトとしてここを参考にした
- https://github.com/msgpack/msgpack-c/blob/master/include/msgpack/object.h#L27-L43
- 以下でobjectのtypeが定義されているらしい
MSGPACK_OBJECT_NIL = 0x00, MSGPACK_OBJECT_BOOLEAN = 0x01, MSGPACK_OBJECT_POSITIVE_INTEGER = 0x02, MSGPACK_OBJECT_NEGATIVE_INTEGER = 0x03, MSGPACK_OBJECT_FLOAT32 = 0x0a, MSGPACK_OBJECT_FLOAT64 = 0x04, MSGPACK_OBJECT_FLOAT = 0x04, #if defined(MSGPACK_USE_LEGACY_NAME_AS_FLOAT) MSGPACK_OBJECT_DOUBLE = MSGPACK_OBJECT_FLOAT, /* obsolete */ #endif /* MSGPACK_USE_LEGACY_NAME_AS_FLOAT */ MSGPACK_OBJECT_STR = 0x05, MSGPACK_OBJECT_ARRAY = 0x06, MSGPACK_OBJECT_MAP = 0x07, MSGPACK_OBJECT_BIN = 0x08, MSGPACK_OBJECT_EXT = 0x09
- 注意点として、msgpackのバージョンによるのか、関数名や引数が参照渡しかポインタ渡しかなどが違うみたい
- 単純に例と同じようにやってもうまく行かなかった
User-defined classes(MSGPACK_DEFINE)
msgpack-c/QUICKSTART-CPP.md at master · msgpack/msgpack-c · GitHub
You can use serialize/deserializes user-defined classes using MSGPACK_DEFINE macro.
とある通り、ユーザ定義のクラスをMSGPACK_DEFINEというマクロを使ってシリアライズ/デシリアライズできる
$ cat msgpack_define.cpp #include <msgpack.hpp> #include <vector> #include <string> #include <iostream> class myclass { //private: // std::string m_str; // std::vector<int> m_vec; public: std::string m_str; std::vector<int> m_vec; MSGPACK_DEFINE(m_str, m_vec); myclass() { m_str = "default"; m_vec.push_back(1); m_vec.push_back(2); m_vec.push_back(3); } }; int main(void) { std::vector<myclass> vec; // add some elements into vec... vec.push_back(myclass()); // you can serialize myclass directly msgpack::sbuffer sbuf; msgpack::pack(sbuf, vec); msgpack::object_handle oh = msgpack::unpack(sbuf.data(), sbuf.size()); msgpack::object obj = oh.get(); // you can convert object to myclass directly std::vector<myclass> rvec; obj.convert(rvec); for(std::vector<myclass>::iterator it = rvec.begin(); it != rvec.end(); ++it) { std::cout << (*it).m_str << std::endl; for(std::vector<int>::iterator m_vec_it = (*it).m_vec.begin(); m_vec_it != (*it).m_vec.end(); ++m_vec_it) { std::cout << (*m_vec_it) << std::endl; } } }
実行結果
$ g++ -Ipath_to_msgpack/include msgpack_define.cpp -o msgpack_define $ ./msgpack_define default 1 2 3
MSGPACK_DEFINEの定義
v1_1_cpp_adaptor · msgpack/msgpack-c Wiki · GitHub
ソースコードは以下 - https://github.com/msgpack/msgpack-c/blob/55b51c506fee9ce496e9b98aca33cadade681479/include/msgpack/adaptor/define_decl.hpp#L28-L42
#define MSGPACK_DEFINE_ARRAY(...) \ template <typename Packer> \ void msgpack_pack(Packer& pk) const \ { \ msgpack::type::make_define_array(__VA_ARGS__).msgpack_pack(pk); \ } \ void msgpack_unpack(msgpack::object const& o) \ { \ msgpack::type::make_define_array(__VA_ARGS__).msgpack_unpack(o); \ }\ template <typename MSGPACK_OBJECT> \ void msgpack_object(MSGPACK_OBJECT* o, msgpack::zone& z) const \ { \ msgpack::type::make_define_array(__VA_ARGS__).msgpack_object(o, z); \ } 同じソースコードに下に以下も定義されているので、MSGPACK_DEFINE がMSGPACK_DEFINE_ARRAYに置き換わる #define MSGPACK_DEFINE MSGPACK_DEFINE_ARRAY