NestJSとC++間でgRPC（④ C++でgRPCクライアントを作成）

# はじめに

### 最終目標
NestJSプログラムとC++プログラムにそれぞれgRPCモジュールを実装して相互通信させることを最終目標とし、目標達成に至るまでのプロセスを段階的に記します。
C++側は公式サンプルにあるCMakeによるコンパイルではなく、Makefileでコンパイルできるようにします。

### 今回の目標
C++でgRPCのクライアントプログラムを作成します。
公式のサンプルにはCMakeでコンパイルする方法しかありませんが、ここではMakefileでコンパイルする方法を試します。

### 環境
NestJSプログラム、C++プログラム共に同一のAlmaLinux8.8上で動作するものとします。

# ファイル構成
```cpp_server```フォルダは前回作成した```grpc_api```と同階層に配置します。
各ファイルについては後ほど説明します。
```
./grpc_api
./cpp_client
    ├ main.cpp
    ├ client.cpp
    ├ client.h
    ├ libs.mk
    └ Makefile
```
# C++でgRPCクライアントプログラムを実装
下記の通り```CppClient```クラスに```GetUserName()```と```SetUser()```を実装します。
```client.h
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include <user.grpc.pb.h>

using namespace grpc;

class CppClient {
 public:
  CppClient(std::shared_ptr<Channel> channel);
  std::string GetUserName(int id); 
  void SetUser(int id, std::string name);

 private:
  std::unique_ptr<user::UsersService::Stub> stub_;
};
```

```client.cpp
#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>
#include <user.grpc.pb.h>
#include "client.h"

using namespace grpc;
using namespace user;

CppClient::CppClient(std::shared_ptr<Channel> channel)
      : stub_(UsersService::NewStub(channel)) {}

std::string CppClient::GetUserName(int id) {
    UserById request;
    request.set_id(id);
    User reply;
    ClientContext context;
    Status status = stub_->GetUser(&context, request, &reply);

    if (status.ok()) {
      return reply.name();
    } else {
      std::cout << status.error_code() << ": " << status.error_message()
                << std::endl;
      return "RPC failed";
    }
}

void CppClient::SetUser(int id, std::string name) {
	User request;
	request.set_id(id);
	request.set_name(name);
	Result reply;
	ClientContext context;
	Status status = stub_->SetUser(&context, request, &reply);
	
    if (status.ok()) {
	    std::cout << "Result: " << reply.message() << std::endl;
    } else {
      std::cout << status.error_code() << ": " << status.error_message()
                << std::endl;
    }
}
```
起動引数に```--id```のみを指定すると```GetUserName()```を実行し、```--name```も指定すると```SetUser()```を実行します。
```main.cpp
#include <iostream>
#include <string>

#include <absl/flags/flag.h>
#include <absl/flags/parse.h>
#include <grpcpp/grpcpp.h>
#include "client.h"

ABSL_FLAG(std::string, host, "localhost:5001", "Server address");
ABSL_FLAG(int, id, 0, "User by ID");
ABSL_FLAG(std::string, name, "", "User Name");

int main(int argc, char** argv) {
	absl::ParseCommandLine(argc, argv);
	std::string host = absl::GetFlag(FLAGS_host);
	int id = absl::GetFlag(FLAGS_id);
	std::string name = absl::GetFlag(FLAGS_name);
	CppClient client(
		grpc::CreateChannel(host, grpc::InsecureChannelCredentials()));
	if(name == "") {
		std::string name = client.GetUserName(id);
		std::cout << "received: " << name << std::endl;
	}
	else{
		client.SetUser(id, name);
	}

	return 0;
}
```

# Makefileの作成
### インクルードパス
gRPCはホームディレクトリの```.local```にインストールされているので、その```include```フォルダと、前回作成したProtocol Buffersのライブラリのヘッダファイルがある```grpc_api/build```をインクルードパスに指定します。
```
INCLUDES := -I../.local/include -I../grpc_api/build
```
### ライブラリパス＆ライブラリリンク
ライブラリパスはProtocol Buffersのライブラリがある```grpc_api/build```、gRPCのインストールフォルダにある```lib```と```lib64```を指定します。
リンクするライブラリにはProtocol Buffersの```user_grpc_proto```と、gRPCのライブラリ群を指定します。
gRPCのライブラリ群はgRPCのサンプルプログラムをコンパイルした時に生成される```link.txt```というファイルから抽出して```libs.mk```として別ファイルにしました。
```libs.mk
LIBDIR += -L../grpc_api/build -L../.local/lib -L../.local/lib64
LIBS += -luser_grpc_proto -labsl_flags_parse -lgrpc++_reflection -lgrpc++ -lprotobuf -lgrpc -lupb_json_lib -lupb_textformat_lib -lupb_message_lib -lupb_base_lib -lupb_mem_lib -lutf8_range_lib -lre2 -lz -lcares -lgpr -labsl_random_distributions -labsl_random_seed_sequences -labsl_random_internal_pool_urbg -labsl_random_internal_randen -labsl_random_internal_randen_hwaes -labsl_random_internal_randen_hwaes_impl -labsl_random_internal_randen_slow -labsl_random_internal_platform -labsl_random_internal_seed_material -labsl_random_seed_gen_exception -lssl -lcrypto -laddress_sorting -ldl -lm -lrt -pthread -labsl_log_internal_check_op -labsl_leak_check -labsl_die_if_null -labsl_log_internal_conditions -labsl_log_internal_message -labsl_log_internal_nullguard -labsl_examine_stack -labsl_log_internal_format -labsl_log_internal_proto -labsl_log_internal_log_sink_set -labsl_log_sink -labsl_log_entry -labsl_log_initialize -labsl_log_globals -labsl_vlog_config_internal -labsl_log_internal_fnmatch -labsl_log_internal_globals -labsl_statusor -labsl_status -labsl_strerror -lutf8_validity -labsl_flags_usage -labsl_flags_usage_internal -labsl_flags_internal -labsl_flags_marshalling -labsl_flags_reflection -labsl_flags_config -labsl_cord -labsl_cordz_info -labsl_cord_internal -labsl_cordz_functions -labsl_cordz_handle -labsl_crc_cord_state -labsl_crc32c -labsl_str_format_internal -labsl_crc_internal -labsl_crc_cpu_detect -labsl_raw_hash_set -labsl_hash -labsl_bad_variant_access -labsl_city -labsl_low_level_hash -labsl_hashtablez_sampler -labsl_exponential_biased -labsl_flags_private_handle_accessor -labsl_flags_commandlineflag -labsl_bad_optional_access -labsl_flags_commandlineflag_internal -labsl_flags_program_name -labsl_synchronization -labsl_graphcycles_internal -labsl_kernel_timeout_internal -labsl_time -labsl_civil_time -labsl_time_zone -labsl_stacktrace -labsl_symbolize -labsl_strings -labsl_strings_internal -labsl_string_view -labsl_int128 -labsl_throw_delegate -labsl_malloc_internal -labsl_debugging_internal -labsl_demangle_internal -labsl_base -lpthread -labsl_raw_logging_internal -labsl_log_severity -labsl_spinlock_wait -lrt  
```
### Makefile
Makefile全体は下記の通りです。
上の```libs.mk```をインクルードしています。
```Makefile
PROGRAM = cpp_client
	
INCLUDES := -I../.local/include -I../grpc_api/build

LIBDIR :=
LIBS :=
-include libs.mk

RM := rm -rf
OBJS := main.o client.o
CC := g++
CFLAGS := -Wall -O2
.SUFFIXES: .cpp .o
.PHONY: all

all: depend $(PROGRAM)
$(PROGRAM): $(OBJS)
	$(CC) -o $(PROGRAM) $^ $(LIBDIR) $(LIBS)

.cpp.o:
	$(CC) $(INCLUDES) $(CFLAGS) -c $<

.PHONY: clean
clean:
	$(RM) $(PROGRAM) $(OBJS) depend.inc

.PHONY: depend
depend: $(OBJS:.o=.cpp)
	-@ $(RM) depend.inc
	-@ for i in $^; do cpp -MM $$i | sed "s/\ [_a-zA-Z0-9][_a-zA-Z0-9]*\.cpp//g" >> depend.inc; done

-include depend.inc
```
# コンパイル
```make```を実行して```cpp_client```が生成されれば成功です。
```
$ make
```
# 動作確認
前回作成したサーバプログラムと通信を行います。

### サーバプログラムの起動
```
$ cd cpp_server
$ ./cpp_server 
Server listening on 0.0.0.0:5001
```
### クライアントプログラムの実行（GetUserName）
下記を実行して```id=3```の```name```を取得します。
```
$ ./cpp_client --id=3
received: User3
```
サーバプログラムの出力結果
```
Received GetUser(id=3)
Reply User (name=User3)
```
### クライアントプログラムの実行（SetUser）
下記を実行して```id=123```の```name=User123```を登録します。
```
$ ./cpp_client --id=123 --name=User123
Result: Success
```
サーバプログラムの出力結果
```
Request SetUser(id=123,name=User123)
```
さらに下記を実行して```id=123```の```name```を取得して正しく登録されたことを確認します。
```
$ ./cpp_client --id=123
received: User123
```
サーバプログラムの出力結果
```
Received GetUser(id=123)
Reply User (name=User123)
```

これでC++側の実装は完了です。
次回はNestJSのサーバ/クライアントプログラムを実装します。

# 関連記事

https://qiita.com/att-unno/items/cf9deb4b507cb5b6bd6d?1

https://qiita.com/att-unno/items/81f6760d3f447e7c3741?1

https://qiita.com/att-unno/items/55970c25da1d8be0503f?1

https://qiita.com/att-unno/items/17a239e661ff09badd99?1

https://qiita.com/att-unno/items/551c8554b5b521091611?1