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

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

> - [最先端の秘密計算技術、格子暗号スタディロードマップを公開！！（エンジニア、リサーチャー必読）](https://qiita.com/kenmaro/items/f2d4fb84833c308a4d29)
-  [秘密計算エンジニアを始めて３年が経った。（ついに機械学習モデルの暗号状態での学習について言及します。）](https://qiita.com/kenmaro/items/a1d90a074d552b7bf886)


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

https://grpc.io/docs/languages/cpp/quickstart/

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

として書いています。

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

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

- [CMakeを使ったクライアント・サーバのビルド](https://qiita.com/kenmaro/items/8792b442f4bd47b33c83)
- [C++でgrpcサーバを実装する](https://qiita.com/kenmaro/items/094803fd8bd50addd055) 
- [C++でgrpcクライアントを実装する](https://qiita.com/kenmaro/items/e9326663cd94f67685d0)
- **[protobufへのオブジェクトの出し入れについて、tips をまとめる](https://qiita.com/kenmaro/items/da3216d62f9891952a66)（　<---- この記事の内容です。）**


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

https://github.com/kenmaro3/grpc_cpp/tree/step4

今回は、使用ブランチを `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``` となっていることに注意して下さい。


## 実行
いつものように

```bash
pushd proto
sh proto.sh
popd
mkdir build
cd build
cmake ..
make -j4
```

でビルドしたら、ターミナルを二つ開いて下さい。
両方ともbuild フォルダに移動し、

```bash
./myserver
```

でサーバを起動します。そして、

```bash
./client
````

でクライアントのコードを走らせて下さい。

サーバ側の関数が実際に呼ばれ、サーバサイドを走らせているターミナル上に


```bash
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.
```

と表示されれば大丈夫です。ちなみに、
クライアントサイドには

```bash
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