Этот девлог рассказывает о путешествии grpcxx — попытке создать лучший gRPC Server API с использованием современного C++ (C++20).
В предыдущих журналах разработки я описал, как использовал классы-шаблоны для определения типов C++ для методов RPC и служб gRPC (Журнал разработчиков № 3) и как я использовал эти классы-шаблоны в реализации сервера gRPC для отправки запросов RPC (Журнал разработчиков № 4). ).
Давайте посмотрим, как собрать все это вместе и реализовать базовую службу gRPC Hello World…
Если вы хотите сначала посмотреть код, он находится в папке examples/helloworld.
Привет Мир 👋
Служба gRPC начинается с определения Protobuf. Вдохновленный официальным примером helloworld.proto
, но также учитывая производственный вариант использования с управлением версиями пакетов, я определил следующую службу;
Листинг 1 — greeter.proto
syntax = "proto3"; package helloworld.v1; service Greeter { rpc Hello(GreeterHelloRequest) returns (GreeterHelloResponse) {} } message GreeterHelloRequest { string name = 1; } message GreeterHelloResponse { string message = 1; }
Теперь я могу использовать вышеуказанный файл .proto
и компилятор protobuf (protoc
) для создания классов C++ для сообщений GreeterHelloRequest
и GreeterHelloResponse
сообщений. Поскольку я использую CMake, я использовал настраиваемое правило сборки для генерации необходимого кода в процессе сборки.
Если вам интересно, Руководство по основам протокольных буферов для C++ более подробно объясняет определения сообщений и генерацию кода.
Обычно компилятор protobuf также генерирует код для службы gRPC (ссылка: Учебник по основам gRPC C++). Но я не реализовал генератор (пока), так что давайте напишем сервисный код.
namespace helloworld { namespace v1 { namespace Greeter { using rpcHello = grpcxx::rpc<"Hello", helloworld::v1::GreeterHelloRequest, helloworld::v1::GreeterHelloResponse>; // [1] using Service = grpcxx::service<"helloworld.v1.Greeter", rpcHello>; // [2] struct ServiceImpl { // [3] template <typename T> typename T::result_type call(const typename T::request_type &) { return {grpcxx::status::code_t::unimplemented, std::nullopt}; } }; }; // namespace Greeter } // namespace v1 } // namespace helloworld
[1] — введите псевдоним для идентификации метода RPC Hello.
[2] — Введите псевдоним для идентификации службы Greeter (с одним методом RPC, идентифицированным псевдонимом типа rpcHello
).
[3] — Удобный класс для реализации сервиса, возвращающий статус UNIMPLEMENTED
gRPC для всех вызовов.
Это всего несколько строк кода, которые, на мой взгляд, намного легче читать и понимать по сравнению с примерно 330 строками кода, сгенерированным официальным grpc_cpp_plugin
.
Код для запуска сервера gRPC будет таким же простым, как;
using namespace helloworld::v1::Greeter; int main() { ServiceImpl greeter; Service service(greeter); // instantiate a service using the convenient implementation grpcxx::server server; server.add(service); server.run("127.0.0.1", 7000); return 0; } ❯ grpcurl -proto examples/helloworld/proto/helloworld/v1/greeter.proto -plaintext localhost:7000 helloworld.v1.Greeter/Hello ERROR: Code: Unimplemented Message:
Но на данный момент он возвращает только ответ UNIMPLEMENTED
, так как в нем отсутствует код приложения, который реализовывал бы методы RPC.
Код приложения, реализующий методы RPC, может просто специфицировать шаблонную функцию call<>()
в ServiceImpl{}.
using namespace helloworld::v1::Greeter; // Implement rpc application logic using template specialisation for `ServiceImpl` template <> rpcHello::result_type ServiceImpl::call<rpcHello>(const GreeterHelloRequest &req) { GreeterHelloResponse res; res.set_message("Hello `" + req.name() + "` 👋"); return {grpcxx::status::code_t::ok, res}; }
Or,
Поскольку не все приложения сделаны из одной ткани, существует также свобода использования реализации, определяемой приложением.
using namespace helloworld::v1::Greeter; // Application defined implementation struct GreeterImpl { template <typename T> typename T::result_type call(const typename T::request_type &) { return {grpcxx::status::code_t::unimplemented, std::nullopt}; } template <> rpcHello::result_type call<rpcHello>(const helloworld::v1::GreeterHelloRequest &req) { helloworld::v1::GreeterHelloResponse res; res.set_message("Hello `" + req.name() + "` 👋"); return {grpcxx::status::code_t::ok, res}; } }; int main() { GreeterImpl greeter; Service service(greeter); grpcxx::server server; server.add(service); server.run("127.0.0.1", 7000); return 0; } ❯ grpcurl -proto examples/helloworld/proto/helloworld/v1/greeter.proto -plaintext -d '{"name": "World"}' localhost:7000 helloworld.v1.Greeter/Hello { "message": "Hello `World` 👋" }
Освобождение кода приложения от оков официального gRPC C++ API было моей главной мотивацией для этого проекта, и я очень доволен тем, как это получилось.
В следующий раз, это слишком медленно? 🐌