diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c84c3d6 --- /dev/null +++ b/CMakeLists.txt @@ -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) diff --git a/buffers/CMakeLists.txt b/buffers/CMakeLists.txt new file mode 100644 index 0000000..3a92c4a --- /dev/null +++ b/buffers/CMakeLists.txt @@ -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) diff --git a/buffers/hello_world.proto b/buffers/hello_world.proto new file mode 100644 index 0000000..433f9dd --- /dev/null +++ b/buffers/hello_world.proto @@ -0,0 +1,11 @@ +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} + +message HelloRequest { + required string name = 1; +} + +message HelloReply { + required string message = 1; +} \ No newline at end of file diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt new file mode 100644 index 0000000..af5997c --- /dev/null +++ b/client/CMakeLists.txt @@ -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} +) diff --git a/client/greeter_client.cpp b/client/greeter_client.cpp new file mode 100644 index 0000000..ba06795 --- /dev/null +++ b/client/greeter_client.cpp @@ -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 +#include +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "hello_world.pb.h" + +#include + +#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) + : 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 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; +} diff --git a/controller/CMakeLists.txt b/controller/CMakeLists.txt new file mode 100644 index 0000000..e66dcb0 --- /dev/null +++ b/controller/CMakeLists.txt @@ -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} +) diff --git a/controller/greeter_server.cpp b/controller/greeter_server.cpp new file mode 100644 index 0000000..b7df077 --- /dev/null +++ b/controller/greeter_server.cpp @@ -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 +#include +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/strings/str_format.h" + +#include "hello_world.pb.h" + +#include +#include +#include +#include +#include +#include + +#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(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; +}