はじめに
最終目標
NestJSプログラムとC++プログラムにそれぞれgRPCモジュールを実装して相互通信させることを最終目標とし、目標達成に至るまでのプロセスを段階的に記します。
C++側は公式サンプルにあるCMakeによるコンパイルではなく、Makefileでコンパイルできるようにします。
今回の目標
C++でgRPCのサーバプログラムを作成します。
公式のサンプルにはCMakeでコンパイルする方法しかありませんが、ここではMakefileでコンパイルする方法を試します。
環境
NestJSプログラム、C++プログラム共に同一のAlmaLinux8.8上で動作するものとします。
ファイル構成
cpp_server
フォルダは前回作成したgrpc_api
と同階層に配置します。
各ファイルについては後ほど説明します。
./grpc_api
./cpp_server
├ main.cpp
├ server.cpp
├ server.h
├ libs.mk
└ Makefile
C++でgRPCサーバプログラムを実装
下記の通り実装します。
前回作成したProtocol Buffersのライブラリの、UsersService
クラスを継承してCppServerServiceImpl
クラスを作成し、GetUser()
とSetUser()
をオーバーライド実装します。
またメンバに、id
をキー、name
を値としたuserList
連想配列を持たせ、この連想配列に対し、GetUser()
で取得、SetUser()
で登録するようにします。
#include <string>
#include <map>
#include <grpcpp/grpcpp.h>
#include <user.grpc.pb.h>
using namespace grpc;
using namespace user;
class CppServerServiceImpl final : public UsersService::Service {
public:
static void RunServer(uint16_t port);
private:
CppServerServiceImpl();
virtual ~CppServerServiceImpl();
Status GetUser(ServerContext* context, const UserById* request, User* reply) override;
Status SetUser(ServerContext* context, const User* request, Result* reply) override;
std::map<int, std::string> userList;
};
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <user.grpc.pb.h>
#include "server.h"
using namespace grpc;
using namespace user;
CppServerServiceImpl::CppServerServiceImpl() {
userList[1] = "User1";
userList[2] = "User2";
userList[3] = "User3";
userList[4] = "User4";
}
CppServerServiceImpl::~CppServerServiceImpl() {}
Status CppServerServiceImpl::GetUser(ServerContext* context, const UserById* request, User* reply) {
int id = request->id();
std::cout << "Received GetUser(id=" << id << ")" << std::endl;
std::string name = "";
Status status;
if(userList.find(request->id()) != userList.end()){
name = userList[id];
status = Status::OK;
}
else {
name = "Not Found";
status = Status::CANCELLED;
}
std::cout << "Reply User (name=" << name << ")" << std::endl;
reply->set_name(name);
return status;
}
Status CppServerServiceImpl::SetUser(ServerContext* context, const User* request, Result* reply) {
userList[request->id()] = request->name();
std::cout << "Request SetUser(id=" << request->id() << ",name=" << request->name() << ")" << std::endl;
reply->set_message("Success");
return Status::OK;
}
void CppServerServiceImpl::RunServer(uint16_t port) {
std::string server_address = absl::StrFormat("0.0.0.0:%d", port);
CppServerServiceImpl service;
grpc::EnableDefaultHealthCheckService(true);
grpc::reflection::InitProtoReflectionServerBuilderPlugin();
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
server->Wait();
}
CppServerServiceImpl::RunServer()
内のserver->Wait()
は処理をブロックします。
明示的に終了処理を実装したい場合はserver->Wait()
ではなく別途ループ処理などを実装し、終了時にループを抜ければunique_ptr
のスコープを外れるとき終了します。
もしくはserver
をクラスメンバにし、終了時は別スレッドからserver->Shutdown()
を実行することでserver->Wait()
のブロックが解除されます。
#include <absl/flags/flag.h>
#include <absl/flags/parse.h>
#include "server.h"
ABSL_FLAG(uint16_t, port, 5001, "Server port for the service");
int main(int argc, char** argv) {
absl::ParseCommandLine(argc, argv);
CppServerServiceImpl::RunServer(absl::GetFlag(FLAGS_port));
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
として別ファイルにしました。
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_server
INCLUDES := -I../.local/include -I../grpc_api/build
LIBDIR :=
LIBS :=
-include libs.mk
RM := rm -rf
OBJS := main.o server.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_server
が生成されれば成功です。
$ make
次回はC++のクライアントプログラムを実装します。
関連記事