gRPC Tutorial

Introduction

gRPC is a modern open source high-performance RPC (Remote Procedure Call) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in the last mile of distributed computing to connect devices, mobile applications, and browsers to backend services.

In this blog post, I would like to a short tutorial on how to use gRPC with C++ and CMake.

Examples

The examples include a hello-world example and an arithmetics example. The implementation and the Docker container are available from the GitHub.

Installation

It is always recommended to use Docker container. The installation process could also be viewed from the Dockerfile.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
FROM ubuntu:20.04

LABEL maintainer="Lei Mao <dukeleimao@gmail.com>"

ARG GPRC_VERSION=1.34.0
ARG NUM_JOBS=8

ENV DEBIAN_FRONTEND=noninteractive

# Install package dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
software-properties-common \
autoconf \
automake \
libtool \
pkg-config \
ca-certificates \
wget \
git \
curl \
vim \
gdb \
valgrind \
cmake
RUN apt-get clean

# gRPC
# https://github.com/grpc/grpc/tree/master/src/cpp
# https://github.com/grpc/grpc/blob/master/BUILDING.md
RUN cd /tmp && \
apt-get install -y build-essential autoconf libtool pkg-config && \
git clone --recurse-submodules -b v${GPRC_VERSION} https://github.com/grpc/grpc && \
cd grpc && \
mkdir -p cmake/build && \
cd cmake/build && \
cmake -DgRPC_INSTALL=ON \
-DgRPC_BUILD_TESTS=OFF \
-DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
../.. && \
make -j${NUM_JOBS} && \
make install

Prepare Protobuf Data Structure

This tutorial assumes you know Protobuf. If you don’t know how to program using Protobuf, please refer to my blog post Google Protocol Buffer Tutorial.

The data structures of the transmission messages will be defined in proto files.

Implement gRPC Client and Server

The gRPC client and server C++ programs will use the gRPC header and source code files of the structured data generated by the Protobuf compiler. We also need Protobuf header and source code because the gRPC header and source code files depend on the Protobuf header and source code. Therefore, the good protocol is, generate Protobuf header and source code using the proto file and the Protobuf Compiler, generate gRPC header and source code using the proto file and the Protobuf Compiler (gRPC plugin), build client/server gRPC library using Protobuf header and source code and gRPC header and source code and linked with gRPC libraries, linked the library to the client and the server program.

Implement CMake Build File

To build gRPC programs, we would use find_package(Protobuf) and find_package(gRPC) for our convenience to find necessary building tools and libraries.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
set(protobuf_MODULE_COMPATIBLE TRUE)
find_package(Protobuf CONFIG REQUIRED)
message(STATUS "Using protobuf ${Protobuf_VERSION}")

set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
set(_REFLECTION gRPC::grpc++_reflection)
if(CMAKE_CROSSCOMPILING)
find_program(_PROTOBUF_PROTOC protoc)
else()
set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)
endif()

# Find gRPC installation
# Looks for gRPCConfig.cmake file installed by gRPC's cmake installation.
find_package(gRPC CONFIG REQUIRED)
message(STATUS "Using gRPC ${gRPC_VERSION}")

set(_GRPC_GRPCPP gRPC::grpc++)
if(CMAKE_CROSSCOMPILING)
find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
else()
set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:gRPC::grpc_cpp_plugin>)
endif()

For each pair of the client and server programs, the following CMake code snippets are quite reusable for different kind of gRPC programs to generate gRPC source files and application building.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
set (proto_name greetings)

# Get proto files
get_filename_component(proto "../../protos/${proto_name}.proto" ABSOLUTE)
get_filename_component(proto_dir "${proto}" DIRECTORY)

# Generate source files
set(proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/${proto_name}.pb.cc")
set(proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/${proto_name}.pb.h")
set(grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/${proto_name}.grpc.pb.cc")
set(grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/${proto_name}.grpc.pb.h")
add_custom_command(
OUTPUT "${proto_srcs}" "${proto_hdrs}" "${grpc_srcs}" "${grpc_hdrs}"
COMMAND ${_PROTOBUF_PROTOC}
ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
--cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
-I "${proto_dir}"
--plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
"${proto}"
DEPENDS "${proto}"
)

set(targets "${proto_name}_server" "${proto_name}_client")

# Include generated *.pb.h files
include_directories(${CMAKE_CURRENT_BINARY_DIR})

foreach(target ${targets})
add_executable(${target} "${target}.cc" ${proto_srcs} ${grpc_srcs})
target_link_libraries(${target} PRIVATE ${_REFLECTION} ${_GRPC_GRPCPP} ${_PROTOBUF_LIBPROTOBUF})
# Cannot do this because these directories are not available during CMake "compile time"
# target_include_directories(${proto_hdrs} ${grpc_hdrs})
endforeach()

Demo

In one terminal, we start the gRPC server.

1
2
$ ./bin/arithmetics_server 
Server listening on 0.0.0.0:50051

In another terminal, we start the gRPC client.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ./bin/arithmetics_client 
Please enter your binary arithmetic expression:
300 + 200
gRPC returned:
500
Please enter your binary arithmetic expression:
300 - 200
gRPC returned:
100
Please enter your binary arithmetic expression:
300 * 200
gRPC returned:
60000
Please enter your binary arithmetic expression:
300 / 200
gRPC returned:
1
Please enter your binary arithmetic expression:

References

Author

Lei Mao

Posted on

06-22-2019

Updated on

12-24-2020

Licensed under


Comments