@kenmaroです。
普段は主に秘密計算、準同型暗号などの記事について投稿しています。
秘密計算に関連するまとめの記事に関しては以下をご覧ください。
概要
grpcをC++を用いて実装するときの、公式チュートリアル
を終えた後に行うと理解が深まるチュートリアル
として書いています。
今回のチュートリアルの構成
以下の流れで記事を分けて書いており、この記事は第四回の内容となります。
- CMakeを使ったクライアント・サーバのビルド
- C++でgrpcサーバを実装する
- C++でgrpcクライアントを実装する
- protobufへのオブジェクトの出し入れについて、tips をまとめる( <---- この記事の内容です。)
準備
前回も使用したチュートリアルレポジトリを使います。
今回は、使用ブランチを step4
として下さい。クローンした後にブランチを移動する場合、
git checkout -b step4 origin/step4
として下さい。
前回とのレポジトリの変更点は
client.cpp
controller/myserver.cpp
となります。
何がしたいのか
前回までのstep3で、クライアントからサーバへの通信は確認しましたが、
前回は簡易的にprotobufをディフォルトコンストラクタでイニシャライズし、
そのままクライアントからサーバに送りました。
しかし、もちろん実際のサービスでは
クライアントが意味のあるデータをサーバに送り、サーバ側ではデータを受け取り、
何か処理を行なってからクライアント側に返すはずです。
そのときに必要になるのが、protobufへの実際のデータの出し入れです。
すなわち、
- クライアント側でデータをprotobufに格納 (1)
- 送る
- サーバはprotobufを受け取って、クラスオブジェクトに変換する (2)
- 処理する
- クライアントに送るためにprotobufに格納 (3)
- 返却する
という流れとなります。このうちの (1), (2), (3) を実際に見ていきます。
protobuf へのデータの出し入れ
string を持つ protobuf への出し入れ
まず、string を持つ PB_Message
のやりとりを見てみます。
message PB_Message{
string message =1;
}
//================================================
printf("[Client] test1 called.\n");
ClientContext context_for_test1;
PB_Message pb_send_for_test1;
PB_Message reply_for_test1;
string message_string = "this is message";
pb_send_for_test1.set_message(message_string);
stub->test1(&context_for_test1, pb_send_for_test1, &reply_for_test1);
printf("[Client] test1 done.\n");
Status test1(ServerContext* context, const PB_Message* request, PB_Message* reply) override {
printf("[ServerService: test1] called.\n");
string message = request->message();
printf("message: %s\n", message.c_str());
string message_reply = "this is reply";
reply->set_message(message_reply);
printf("[ServerService: test1] done.\n");
return Status::OK;
}
message
は、string 型を持っていますが、これは簡単に出し入れすることができます。
特に問題ないでしょう。
repeated を含むprotobuf
次に、repeated double を含む PB_DoubleList
を見てみます。
message PB_DoubleList {
repeated double data = 1;
}
//================================================
printf("[Client] test2 called.\n");
ClientContext context_for_test2;
PB_DoubleList pb_send_for_test2;
PB_DoubleList reply_for_test2;
vector<double> tmp1 = {1,2,3};
google::protobuf::RepeatedField<double> tmp2{tmp1.begin(), tmp1.end()};
pb_send_for_test2.data() = tmp2;
stub->test2(&context_for_test2, pb_send_for_test2, &reply_for_test2);
vector<double> tmp2_2(reply_for_test2.data().size());
for(int i=0; i<reply_for_test2.data().size(); i++){ tmp2_2[i] = reply_for_test2.data()[i];}
for(int i=0; i<reply_for_test2.data().size(); i++){ printf("%f, ", tmp2_2[i]);}
printf("[Client] test2 done.\n");
Status test2(ServerContext* context, const PB_DoubleList* request, PB_DoubleList* reply) override {
printf("[ServerService: test2] called.\n");
vector<double> tmp(request->data().size());
for(int i=0; i<request->data().size(); i++) { tmp[i] = request->data()[i];}
for(int i=0; i<request->data().size(); i++) { tmp[i] += 1;}
google::protobuf::RepeatedField<double> tmp_field{tmp.begin(), tmp.end()};
reply->data() = tmp_field;
printf("[ServerService: test2] done.\n");
return Status::OK;
}
解説するよりコードを見た方が早いと思いますが、
出し入れはこのように行います。(結構めんどくさいです。)
ポイントとしては、protobuf 本体の PB_DoubleList.data()
には google::protobuf::RepeatedField<double>
が入る、ということです。vector<double>
をそのまま入れることはできません。
message を repeated したものを持つ protobufへの出し入れ
message PB_Data {
repeated PB_DoubleList data = 1;
}
//================================================
printf("[Client] test3 called.\n");
ClientContext context_for_test3;
PB_Data pb_send_for_test3;
PB_Data reply_for_test3;
vector<PB_DoubleList> tmp3;
for(int i=0; i<3; i++){
PB_DoubleList tmp4;
google::protobuf::RepeatedField<double> tmp5{tmp1.begin(), tmp1.end()};
tmp4.data() = tmp5;
tmp3.push_back(tmp4);
}
google::protobuf::RepeatedPtrField<PB_DoubleList> tmp6{tmp3.begin(), tmp3.end()};
pb_send_for_test3.data() = tmp6;
stub->test3(&context_for_test3, pb_send_for_test3, &reply_for_test3);
printf("[Client] test3 done.\n");
Status test3(ServerContext* context, const PB_Data* request, PB_Data* reply) override {
printf("[ServerService: test3] called.\n");
vector<vector<double>> tmp1;
for(int i=0; i<request->data().size(); i++){
vector<double> tmp2;
for(int j=0; j<request->data()[i].data().size(); j++){
tmp2.push_back(request->data()[i].data()[j]);
}
tmp1.push_back(tmp2);
}
for(int i=0; i<tmp1.size(); i++){
print_vec<double>(tmp1[i]);
}
vector<PB_DoubleList> tmp3;
for(int i=0; i<tmp1.size(); i++){
PB_DoubleList tmp4;
google::protobuf::RepeatedField<double> tmp5{tmp1[i].begin(), tmp1[i].end()};
tmp4.data() = tmp5;
tmp3.push_back(tmp4);
}
google::protobuf::RepeatedPtrField<PB_DoubleList> tmp6{tmp3.begin(), tmp3.end()};
reply->data() = tmp6;
printf("[ServerService: test3] done.\n");
return Status::OK;
}
ややこしくなりましたが、これもコードを読んだ方が理解が早いかと思います。
前述のrepeated double のケースと同じで、
protobuf の repeated には、 google::protobuf::RepeatedPtrField<PB_DoubleList>
を格納する必要があります。
このとき、repeated double の時は RepeatedField
だったものが、
repeated PB_DoubleList の時に RepeatedPtrField
となっていることに注意して下さい。
実行
いつものように
pushd proto
sh proto.sh
popd
mkdir build
cd build
cmake ..
make -j4
でビルドしたら、ターミナルを二つ開いて下さい。
両方ともbuild フォルダに移動し、
./myserver
でサーバを起動します。そして、
./client
でクライアントのコードを走らせて下さい。
サーバ側の関数が実際に呼ばれ、サーバサイドを走らせているターミナル上に
root@8233ca8d11d9:/grpc_cpp/build# ./myserver
hello, world
Server listening on 0.0.0.0:5001
[ServerService: test1] called.
message: this is message
[ServerService: test1] done.
[ServerService: test2] called.
[ServerService: test2] done.
[ServerService: test3] called.
1, 2, 3,
1, 2, 3,
1, 2, 3,
[ServerService: test3] done.
と表示されれば大丈夫です。ちなみに、
クライアントサイドには
root@8233ca8d11d9:/grpc_cpp/build# ./client
hello, world!
[Client] test1 called.
[Client] test1 done.
[Client] test2 called.
2.000000, 3.000000, 4.000000, [Client] test2 done.
[Client] test3 called.
[Client] test3 done.
と表示されるはずです。
以上でチュートリアルは終了となります、お疲れ様でした。
まとめ
C++ で実装するgrpc についての記事があまり充実していなかった(気がした)ため、
今回は私が知っているgrpcについての知識を全て書いていくべく、
数回にわけてチュートリアル(公式のチュートリアルの次に行いたいこと)をまとめて行ってきました。
今回は第四回として、protobufへのオブジェクトの出し入れについて、
特にrepeated を含むprotobufへのデータの出し入れについて書きました。
意外と出てこない部分なのでまとめています。
誰かの役に立てれば幸いです。
今回で一旦思っているところまで書くことができたため、
チュートリアルとしては終了と考えています。
細かいオプションの渡し方など、他にも書き残しておきたいところがあるため、
そのあたりも時間を見つけてまとめてみようと思いますが、今チュートリアルはこの辺にしておこうと思います。
今回はこの辺で。