5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C++ を用いて grpc を実装したい人生だった。【repeated をもつprotobufへのデータの出し入れ】

Last updated at Posted at 2022-02-15

@kenmaroです。
普段は主に秘密計算、準同型暗号などの記事について投稿しています
秘密計算に関連するまとめの記事に関しては以下をご覧ください。

概要

grpcをC++を用いて実装するときの、公式チュートリアル

を終えた後に行うと理解が深まるチュートリアル

として書いています。

今回のチュートリアルの構成

以下の流れで記事を分けて書いており、この記事は第四回の内容となります。

準備

前回も使用したチュートリアルレポジトリを使います。

今回は、使用ブランチを 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 のやりとりを見てみます。

protos/model/message.proto
message PB_Message{
    string message =1;
}
client.cpp

  //================================================
  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");

controller/myserver.cpp
  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
を見てみます。

protos/model/doubleList.proto
message PB_DoubleList {
    repeated double data = 1;
}
client.cpp
  //================================================
  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");

controller/myserver.cpp
  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への出し入れ

protos/model/data.proto
message PB_Data {
    repeated PB_DoubleList data = 1;
}
client.cpp
  //================================================
  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");
controller/myserver.cpp
  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へのデータの出し入れについて書きました。
意外と出てこない部分なのでまとめています。

誰かの役に立てれば幸いです。

今回で一旦思っているところまで書くことができたため、
チュートリアルとしては終了と考えています。

細かいオプションの渡し方など、他にも書き残しておきたいところがあるため、
そのあたりも時間を見つけてまとめてみようと思いますが、今チュートリアルはこの辺にしておこうと思います。

今回はこの辺で。

@kenmaro

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?