implement gRPC demo

This commit is contained in:
Fabian Posch 2025-01-14 14:50:41 +01:00
parent 59cb8c23e4
commit 5521d510ad
7 changed files with 428 additions and 0 deletions

86
CMakeLists.txt Normal file
View file

@ -0,0 +1,86 @@
cmake_minimum_required(VERSION 3.20)
project(gRPCTest C CXX)
# Options to build certain parts of the application
option(BUILD_CLIENT "Build the gRPC client." ON)
option(BUILD_SERVER "Build the gRPC server." ON)
# If you do not have gRPC isntalled through a package manager, you can set the custom install location here
# so CMake can find the libraries
set(GRPC_INSTALL_LOCATION "" CACHE PATH "Custom gRPC installation location.")
cmake_path(ABSOLUTE_PATH GRPC_INSTALL_LOCATION)
# set required C++ version
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
add_compile_options(-Wall
-Wextra
-Wconversion-null
-Wmissing-declarations
-Woverlength-strings)
add_compile_options(
-Wpointer-arith
-Wunused-local-typedefs
-Wunused-result
-Wvarargs
-Wvla
-Wwrite-strings
-Wformat-security
-Wundef)
add_compile_options(-O2)
# Find the protoc protobuffer compiler
find_program(_PROTOBUF_PROTOC protoc)
# Find the required gRPC libraries
# Since we have the option to link dynamically, we have to be explicit
find_library(
_PROTOBUF_LIBPROTOBUF
protobuf
HINTS ${GRPC_INSTALL_LOCATION}/lib/
REQUIRED
)
find_library(
_GRPC_GRPCPP_REFLECTION
grpc++_reflection
HINTS ${GRPC_INSTALL_LOCATION}/lib/
REQUIRED
)
find_library(
_GRPC_GRPCPP
grpc++
HINTS ${GRPC_INSTALL_LOCATION}/lib/
REQUIRED
)
find_library(
_GRPC_GRPC
grpc
HINTS ${GRPC_INSTALL_LOCATION}/lib/
REQUIRED
)
find_library(
_GRPC_GPR
gpr
HINTS ${GRPC_INSTALL_LOCATION}/lib/
REQUIRED
)
# Configure the location of the gRPC protoc plugin
find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
# gRPC uses the Abseil package
find_package(absl REQUIRED)
# Include the protobuffers
add_subdirectory(buffers)
# Include the client
if(BUILD_CLIENT)
add_subdirectory(client)
endif(BUILD_CLIENT)
# Include the controller server
if(BUILD_SERVER)
add_subdirectory(controller)
endif(BUILD_SERVER)

77
buffers/CMakeLists.txt Normal file
View file

@ -0,0 +1,77 @@
# Generate the source files from the protobuf files.
# These will be bundled into a statically linked library and
# linked into the applications.
# library name
set(PROTOBUFFER_LIB "grpc_proto_buffers")
set(PROTOBUFFER_LIB ${PROTOBUFFER_LIB} PARENT_SCOPE)
# protoc needs the folder in which all the buffer files reside
# in case there is an import
set(protobuf_src_folder ".")
cmake_path(ABSOLUTE_PATH protobuf_src_folder)
# Grab all the protobuffer files
file(
GLOB BUFFER_SRCS
"*.proto"
)
# Initialize the lists of generated files
set(proto_srcs)
set(proto_hdrs)
set(grpc_srcs)
set(grpc_hdrs)
# Generate all headers and buffer sources
foreach(buffer_src ${BUFFER_SRCS})
# Get the name stem of the file
cmake_path(GET buffer_src STEM buffer_name)
# Set the names for all generated files
set(buffer_proto_src "${CMAKE_CURRENT_BINARY_DIR}/${buffer_name}.pb.cc")
set(buffer_proto_hdr "${CMAKE_CURRENT_BINARY_DIR}/${buffer_name}.pb.h")
set(buffer_grpc_src "${CMAKE_CURRENT_BINARY_DIR}/${buffer_name}.grpc.pb.cc")
set(buffer_grpc_hdr "${CMAKE_CURRENT_BINARY_DIR}/${buffer_name}.grpc.pb.h")
# Generate
add_custom_command(
OUTPUT "${buffer_proto_src}" "${buffer_proto_hdr}" "${buffer_grpc_src}" "${buffer_grpc_hdr}"
COMMAND ${_PROTOBUF_PROTOC}
ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
--cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
-I "${protobuf_src_folder}"
--plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
"${buffer_src}"
DEPENDS "${buffer_src}"
)
# Add the files to the list of generated files
list(APPEND proto_srcs ${buffer_proto_src})
list(APPEND proto_hdrs ${buffer_proto_hdr})
list(APPEND grpc_srcs ${buffer_grpc_src})
list(APPEND grpc_hdrs ${buffer_grpc_hdr})
endforeach(buffer_src)
# Include the generated headers
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Build the library
add_library(
${PROTOBUFFER_LIB}
${proto_srcs}
${proto_hdrs}
${grpc_srcs}
${grpc_hdrs}
)
target_link_libraries(
${PROTOBUFFER_LIB}
absl::check
${_GRPC_GRPCPP_REFLECTION}
${_GRPC_GRPCPP}
${_PROTOBUF_LIBPROTOBUF}
)
# Make the headers available to other parts of the program
set(PROTOBUFFER_HDRS_DIR "${CMAKE_CURRENT_BINARY_DIR}" PARENT_SCOPE)

11
buffers/hello_world.proto Normal file
View file

@ -0,0 +1,11 @@
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
required string name = 1;
}
message HelloReply {
required string message = 1;
}

27
client/CMakeLists.txt Normal file
View file

@ -0,0 +1,27 @@
# Include the headers generated by the protobuffers
include_directories(${PROTOBUFFER_HDRS_DIR}/)
file(
GLOB CLIENT_SRCS
"*.cpp"
)
# Add the main executable
add_executable(
${PROJECT_NAME}
${CLIENT_SRCS}
)
target_link_libraries(${PROJECT_NAME}
${PROTOBUFFER_LIB}
absl::check
absl::flags
absl::flags_parse
absl::log
${_GRPC_GRPC}
${_GRPC_GRPCPP}
${_GRPC_GRPCPP_REFLECTION}
${_PROTOBUF_LIBPROTOBUF}
${_GRPC_GPR}
)

92
client/greeter_client.cpp Normal file
View file

@ -0,0 +1,92 @@
/*
*
* Copyright 2015 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <iostream>
#include <memory>
#include <string>
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "hello_world.pb.h"
#include <grpcpp/grpcpp.h>
#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "hello_world.grpc.pb.h"
#endif
ABSL_FLAG(std::string, target, "localhost:50051", "Server address");
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
class GreeterClient {
public:
GreeterClient(std::shared_ptr<Channel> channel)
: stub_(Greeter::NewStub(channel)) {}
// Assembles the client's payload, sends it and presents the response back
// from the server.
std::string SayHello(const std::string& user) {
// Data we are sending to the server.
HelloRequest request;
request.set_name(user);
// Container for the data we expect from the server.
HelloReply reply;
// Context for the client. It could be used to convey extra information to
// the server and/or tweak certain RPC behaviors.
ClientContext context;
// The actual RPC.
Status status = stub_->SayHello(&context, request, &reply);
// Act upon its status.
if (status.ok()) {
return reply.message();
} else {
std::cout << status.error_code() << ": " << status.error_message()
<< std::endl;
return "RPC failed";
}
}
private:
std::unique_ptr<Greeter::Stub> stub_;
};
int main(int argc, char** argv) {
absl::ParseCommandLine(argc, argv);
// Instantiate the client. It requires a channel, out of which the actual RPCs
// are created. This channel models a connection to an endpoint specified by
// the argument "--target=" which is the only expected argument.
std::string target_str = absl::GetFlag(FLAGS_target);
// We indicate that the channel isn't authenticated (use of
// InsecureChannelCredentials()).
GreeterClient greeter(
grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()));
std::string user("world");
std::string reply = greeter.SayHello(user);
std::cout << "Greeter received: " << reply << std::endl;
return 0;
}

28
controller/CMakeLists.txt Normal file
View file

@ -0,0 +1,28 @@
# Include the headers generated by the protobuffers
include_directories(${PROTOBUFFER_HDRS_DIR})
file(
GLOB CONTROLLER_SRCS
"*.cpp"
)
# Add the main executable
add_executable(
${PROJECT_NAME}_controller
${CONTROLLER_SRCS}
)
target_link_libraries(${PROJECT_NAME}_controller
${PROTOBUFFER_LIB}
absl::check
absl::flags
absl::flags_parse
absl::log
${_GRPC_GRPC}
${_GRPC_GRPCPP}
${_GRPC_GRPCPP_REFLECTION}
${_PROTOBUF_LIBPROTOBUF}
${_GRPC_GPR}
)

View file

@ -0,0 +1,107 @@
/*
*
* Copyright 2015 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <iostream>
#include <memory>
#include <string>
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/strings/str_format.h"
#include "hello_world.pb.h"
#include <grpcpp/server_context.h>
#include <grpcpp/support/server_callback.h>
#include <grpcpp/support/status.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "hello_world.grpc.pb.h"
#endif
void RunServer(uint16_t port);
using grpc::Server;
using grpc::ServerBuilder;
ABSL_FLAG(uint16_t, port, 50051, "Server port for the service");
// This is the implementation of the actual service;
// We elect to implement this as a callback server (preferred implementation)
class GreeterServiceImpl final : public Greeter::CallbackService {
public:
// We only reply with a single message back, thus we use a unary reactor
grpc::ServerUnaryReactor *
SayHello([[maybe_unused]] grpc::CallbackServerContext *context,
const HelloRequest *request, HelloReply *reply) override {
// To be able to handle more fancy things, we elect to implement our own
// reactor
class HelloReactor : public grpc::ServerUnaryReactor {
public:
HelloReactor(const HelloRequest &request, const std::string &prefix,
HelloReply *reply) {
reply->set_message(prefix + request.name());
Finish(grpc::Status::OK);
}
private:
void OnDone() override {
std::cout << "RPC completed." << std::endl;
delete this;
}
void OnCancel() override { std::cerr << "RPC cancelled." << std::endl; }
};
return new HelloReactor(*request, "Hello ", reply);
}
};
void RunServer(uint16_t port) {
std::string server_address = absl::StrFormat("0.0.0.0:%d", port);
GreeterServiceImpl service;
grpc::EnableDefaultHealthCheckService(true);
grpc::reflection::InitProtoReflectionServerBuilderPlugin();
ServerBuilder builder;
// Listen on the given address without any authentication mechanism.
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
// Register "service" as the instance through which we'll communicate with
// clients. In this case it corresponds to an *synchronous* service.
builder.RegisterService(&service);
// Finally assemble the server.
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
// Wait for the server to shutdown. Note that some other thread must be
// responsible for shutting down the server for this call to ever return.
server->Wait();
}
int main(int argc, char **argv) {
absl::ParseCommandLine(argc, argv);
RunServer(absl::GetFlag(FLAGS_port));
return 0;
}