@kenmaroです。
普段は主に秘密計算、準同型暗号などの記事について投稿しています。
秘密計算に関連するまとめの記事に関しては以下をご覧ください。
概要
grpcをC++を用いて実装するときの、公式チュートリアル
を終えた後に行うと理解が深まるチュートリアル
として書いています。
今回のチュートリアルの構成
以下の流れで記事を分けて書いており、この記事は第三回の内容となります。
- CMakeを使ったクライアント・サーバのビルド
- C++でgrpcサーバを実装する
- C++でgrpcクライアントを実装する( <---- この記事の内容です。)
- protobufへのオブジェクトの出し入れについて、tips をまとめる
準備
前回も使用したチュートリアルレポジトリを使います。
今回は、使用ブランチを step3
として下さい。クローンした後にブランチを移動する場合、
git checkout -b step3 origin/step3
として下さい。
前回とのレポジトリの変更点は
client.cpp
のみとなります。
クライアントサイドの実装
# include "controller/common.h"
using namespace std;
using namespace grpc;
int main(){
printf("hello, world!\n");
//auto channel = CreateChannel("localhost:5001", InsecureChannelCredentials());
//auto stub = MyServer::NewStub(channel);
std::shared_ptr<grpc::Channel> channel = CreateChannel("localhost:5001", InsecureChannelCredentials());
std::unique_ptr< MyServer::Stub> stub = MyServer::NewStub(channel);
//================================================
printf("[Client] test1 called.\n");
ClientContext context_for_test1;
PB_Message pb_send_for_test1;
PB_Message reply_for_test1;
stub->test1(&context_for_test1, pb_send_for_test1, &reply_for_test1);
printf("[Client] test1 done.\n");
//================================================
printf("[Client] test2 called.\n");
ClientContext context_for_test2;
PB_DoubleList pb_send_for_test2;
PB_DoubleList reply_for_test2;
stub->test2(&context_for_test2, pb_send_for_test2, &reply_for_test2);
printf("[Client] test2 done.\n");
//================================================
printf("[Client] test3 called.\n");
ClientContext context_for_test3;
PB_Data pb_send_for_test3;
PB_Data reply_for_test3;
stub->test3(&context_for_test3, pb_send_for_test3, &reply_for_test3);
printf("[Client] test3 done.\n");
}
追加された上のコードでは、
サーバサイドの3つの関数
service MyServer{
rpc test1(PB_Message) returns (PB_Message){};
rpc test2(PB_DoubleList) returns (PB_DoubleList){};
rpc test3(PB_Data) returns (PB_Data){};
}
をそれぞれ呼んでいます。
注意点として、
- ClientContextのポインタ
- 関数の入力となるものの実体
- 関数の出力となるもののポインタ
を渡す必要があるということです。
また、stubは auto でイニシャライズすることができますが、
明示的に型を一応書いています。
このように、stubはunique_pointer の形でイニシャライズされるため、
stubが関数を超えてメモリに残り、メモリリークなどを起こしてしまうことは考えなくてよいような設計となっているようです。
実行
いつものように
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.
[ServerService: test1] done.
[ServerService: test2] called.
[ServerService: test2] done.
[ServerService: test3] called.
[ServerService: test3] done.
と表示されれば大丈夫です。ちなみに、
クライアントサイドには
root@8233ca8d11d9:/grpc_cpp/build# ./client
hello, world!
[Client] test1 called.
[Client] test1 done.
[Client] test2 called.
[Client] test2 done.
[Client] test3 called.
[Client] test3 done.
と表示されるはずです。
次回は具体的にprotobufにデータを出し入れします
今回は、簡易的にprotobufをディフォルトコンストラクタでイニシャライズし、
そのままクライアントからサーバに送りました。
しかし、もちろん実際のサービスでは
クライアントが意味のあるデータをサーバに送り、サーバ側ではデータを受け取り、
何か処理を行なってからクライアント側に返すはずです。
そのときに必要になるのが、protobufへの実際のデータの出し入れです。
すなわち、
- クライアント側でデータをprotobufに格納 (1)
- 送る
- サーバはprotobufを受け取って、クラスオブジェクトに変換する (2)
- 処理する
- クライアントに送るためにprotobufに格納 (3)
- 返却する
という流れとなります。このうちの (1), (2), (3) を実際にコーディングしてみます。
まとめ
C++ で実装するgrpc についての記事があまり充実していなかった(気がした)ため、
今回は私が知っているgrpcについての知識を全て書いていくべく、
数回にわけてチュートリアル(公式のチュートリアルの次に行いたいこと)をまとめて行っています。
今回は第三回として、クライアントサイドを簡易的に実装しました。
誰かの役に立てれば幸いです。
次回も是非ご覧ください。
今回はこの辺で。