前回、Protocol Bufferを使って通信したいメッセージのテンプレートを作成するところまで行った。
次に、このテンプレートを使ってプログラム間の通信を行ってみる。 通信と言っても、テキストファイルを読み出した上でプロトコルバッファに格納していき、それをバイナリファイルとして出力、その後別のプログラムでそのバイナリファイルを呼び出して複合するという処理だ。
プロトコルバッファを用いてテキストファイルの送受信を行う
まずは例を参照しよう。GoogleのProtocol Bufferの公式サイトに載っている例が最も簡単だ。
Protocol Buffer Basics: C++ | Protocol Buffers | Google Developers
これを参考に、自分でもプログラムを構築してみた。以下に登録した。
プロトコルバッファのメッセージ形式を定義する
メッセージ形式は前回のものと基本的には変わらないが、もう一つラッパーを加えることで、Personメッセージ構造体を複数定義できるように変更する。それがPersonList Message形式だ。
syntax = "proto3"; message Person { string name = 1; int32 age = 2; } message PersonList { repeated Person person = 1; }
上記をPerson.protoとして保存してprotobufを使ってコンパイルしC++のコードを生成する。
protoc --cpp_out ./ Person.proto
プロトコルバッファを使ってテキストをシリアライズ化する
次に、Googleのサンプルプログラムを用いて以下のようなプログラムを作成した。 名前と年齢が対になっているテキストファイルを読み込んで、プロトコルバッファに登録していく。
$ head Person.file ipjdp5kD66EJn5zu 50 8zhebItQzHcYpXBV 49 VTPFIgHzLKgPiFb8 92 6bHeL4NUHPcdRsxB 61 dtBgzL2DvLJ4F4pb 91 FQkX3WNckgAcf5zR 82 Zka0XGfrzmrSQFLJ 14 Hgxrnc2kzPWgFCoY 4 DEnnFraVvZdQepCM 87 oYO1vdtmZPubJZVh 58 ...
これをプロトコルバッファに登録するのが以下のコードだ。
- write.cpp
PersonList person_list; while (!ifs.eof()) { std::string str_name; int32_t age; ifs >> str_name; ifs >> age; Person *person = person_list.add_person(); // std::cout << str_name << '\n'; // std::cout << age << '\n'; person->set_name (str_name); person->set_age (age); } std::ofstream ofs; ofs.open ("person.dat", std::ios::out | std::ios::trunc | std::ios::binary); if (!person_list.SerializeToOstream(&ofs)) { std::cerr << "Failed to write address book." << std::endl; return -1; }
PersonListクラスのインスタンスを1つ生成し、そこでadd_person()を呼び出すことでPersonメッセージ1つ分を格納できるインスタンスのポインタが取得できる。 このポインタに対して情報の登録を行っていく、という訳だ。
プロトコルバッファへの登録プログラム(write.cpp)のコンパイル
コンパイルおよび実行は、以下のようにして行う。今回は、Person.fileに100000個のデータを格納して実行してみた。
$ g++ -std=c++11 write.cpp Person.pb.cc -o write `pkg-config --cflags --libs protobuf` $ ./write Person.file $ls ... -rw-rw-r-- 1 vagrant vagrant 2197936 Jul 24 13:59 person.dat -rw-rw-r-- 1 vagrant vagrant 1990074 Jul 24 13:59 Person.file ...
person.datがプロトコルバッファから出力されたバイナリデータだが、データ量は削減されるどころか増えてしまった。データ量が少ないのか、データの質が良くないのか、まだ良く分からない。
プロトコルバッファのデータを読み込んでデシリアライズ化する
次にプロトコルバッファのデータを読み込み、バイナリからデシリアイライズして元のデータに戻してみる。
これもGoogleのサンプルプログラムを用い、以下のようなコードを作成した。
- read.cpp
std::fstream ifs; ifs.open(argv[1], std::ios::in | std::ios::binary); if (!person_list.ParseFromIstream(&ifs)) { std::cerr << "Failed to parse address book." << std::endl; return -1; } std::ofstream ofs; ofs.open("output.txt"); for (int i = 0; i < person_list.person_size(); i++) { Person person = person_list.person(i); ofs << person.name () << ' ' << person.age () << '\n'; }
ParseFromIstream
でファイルからプロトコルバッファのバイナリデータを読み込み、そして複合する。それぞれの複合されたデータはperson_size()
個分のデータとして保持され、それらを一つずつテキストデータとして出力している。
プロトコルバッファからのデシリアライズプログラム(read.cpp)のコンパイル
$ g++ -std=c++11 read.cpp Person.pb.cc -o read `pkg-config --cflags --libs protobuf` $ ./read person.dat $ less output.txt ipjdp5kD66EJn5zu 50 8zhebItQzHcYpXBV 49 VTPFIgHzLKgPiFb8 92 6bHeL4NUHPcdRsxB 61 dtBgzL2DvLJ4F4pb 91 FQkX3WNckgAcf5zR 82 Zka0XGfrzmrSQFLJ 14 Hgxrnc2kzPWgFCoY 4 DEnnFraVvZdQepCM 87 oYO1vdtmZPubJZVh 58 ...
正しくデシリアライズ化された。
まとめ
Google のプロトコルバッファ protobuf の基本的な使い方については一通り学ぶことができた。ただしファイルサイズの問題や、速度の問題については未検証で、実際にこのライブラリを使うとなると、有用なデータを用いて検証しなければならないと思う。