0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-07-03

はじめに

最終目標

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のインストールフォルダにあるliblib64を指定します。
リンクするライブラリには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をインクルードしています。

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=3nameを取得します。

$ ./cpp_client --id=3
received: User3

サーバプログラムの出力結果

Received GetUser(id=3)
Reply User (name=User3)

クライアントプログラムの実行(SetUser)

下記を実行してid=123name=User123を登録します。

$ ./cpp_client --id=123 --name=User123
Result: Success

サーバプログラムの出力結果

Request SetUser(id=123,name=User123)

さらに下記を実行してid=123nameを取得して正しく登録されたことを確認します。

$ ./cpp_client --id=123
received: User123

サーバプログラムの出力結果

Received GetUser(id=123)
Reply User (name=User123)

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

関連記事

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?