gRPC C + + tutorial

I have written a tutorial about using gRPC to send and receive messages in Python before. Please refer to the article Experience gRPC . Recently, it is planned to use gRPC in C + + project, so write an article to record how to use C + + language to implement a simple gRPC server and client program.

This tutorial needs to install gRPC first. For the installation tutorial of gRPC, please refer to the article Centros 7 installing gRPCExperience gRPC.

The program involved in this paper includes four parts: client source code client.cc, server source code server.cc, proto file mathtest.proto and Makefile.

Makefile

Makefile is used to compile the source code. The makefile file is as follows:

LDFLAGS = -L/usr/local/lib `pkg-config --libs protobuf grpc++`\
           -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed\
           -ldl

CXX = g++
CPPFLAGS += `pkg-config --cflags protobuf grpc`
CXXFLAGS += -std=c++11

GRPC_CPP_PLUGIN = grpc_cpp_plugin
GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)`

all: client server

client: mathtest.pb.o mathtest.grpc.pb.o client.o
        $(CXX) $^ $(LDFLAGS) -o $@

server: mathtest.pb.o mathtest.grpc.pb.o server.o
        $(CXX) $^ $(LDFLAGS) -o $@

%.grpc.pb.cc: %.proto
        protoc --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $<

%.pb.cc: %.proto
        protoc --cpp_out=. $<

clean:
        rm -f *.o *.pb.cc *.pb.h client server

Executing the Makefile will generate executable client and server executable files.
Both the client and the server executable depend on the source code generated by the proto file. The two rules of Makefile before clean are used to generate these codes.

Some special symbols appear in the Makefile file:

  • $@: target name of the rule, such as client and server in the Makefile above
  • %: matching characters, such as matching mathtest.proto in Makefile above
  • $< the first target name of the dependency, such as mathtest.proto in the Makefile above
  • $^: all dependent target names. In the above server rule in Makefile, they represent mathtest.pb.o mathtest.grpc.pb.o server.o

For the analysis of Makefile, please refer to Ruan Yifeng's Make command tutorial.

Proto

The. proto file is used to define the message formats of clients and servers.

syntax = "proto3";

option java_package = "ex.grpc";

package mathtest;

// Defines the service
service MathTest {
    // Function invoked to send the request
    rpc sendRequest (MathRequest) returns (MathReply) {}
}

// The request message containing requested numbers
message MathRequest {
    int32 a = 1;
    int32 b = 2;
}

// The response message containing response
message MathReply {
    int32 result = 1;
}
  • Syntax defines the syntax for using proto 3 (and proto 2)
  • Java package is used to define the package where the Java class is generated
  • Package means the package name defining the message. For C + + programs, the message class will be wrapped in the corresponding namespace. For example, use mathtest::MathRequest to reference the MathRequest message
  • Service is used to define the service interface in RPC calls. Here we define a service interface sendRequest, which accepts a MathRequest message and returns a MathReply message
  • Message is used to define the fields of the message. For example, a MathRequest message contains two integer fields a and b, and a MathReply contains one integer field result. The number after the field does not represent the value of the field, but the proto program is used to generate the relevant source code.

For the introduction of Protocol Buffers, please refer to the official documents, https://developers.google.com/protocol-buffers/docs/overview.

Server side

server.cc is as follows:

// 
// server.cc
// Created by leo on 2020/1/31.
//

#include <string>

#include <grpcpp/grpcpp.h>
#include "mathtest.grpc.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;

using mathtest::MathTest;
using mathtest::MathRequest;
using mathtest::MathReply;

class MathServiceImplementation final : public MathTest::Service {
    Status sendRequest(
            ServerContext* context,
            const MathRequest* request,
            MathReply* reply
    ) override {
        int a = request->a();
        int b = request->b();

        reply->set_result(a * b);

        return Status::OK;
    }
};

void Run() {
    std::string address("0.0.0.0:5000");
    MathServiceImplementation service;

    ServerBuilder builder;

    builder.AddListeningPort(address, grpc::InsecureServerCredentials());
    builder.RegisterService(&service);

    std::unique_ptr<Server> server(builder.BuildAndStart());
    std::cout << "Server listening on port: " << address << std::endl;

    server->Wait();
}

int main(int argc, char** argv) {
    Run();

    return 0;
}

At the beginning of the code, we introduce the grpc header file and the header file generated by proto, and then we declare the types under the corresponding namespace used in the server-side code.

MathServiceImplementation implements the service interface sendRequest(), which takes the values of a and b fields in the request message and returns the product of a * b. MathServiceImplementation is an implementation class that inherits from MathTest::Service, which is declared in mathtest.grpc.pb.h.

The Run() function specifies the service address of 0.0.0.0:5000. After registering the MathServiceImplementation service, we start the server and accept the client's request.

Client

The client.cc code is as follows:

//
// client.cc
// Created by leo on 2020/1/31.
//

#include <string>

#include <grpcpp/grpcpp.h>
#include "mathtest.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;

using mathtest::MathTest;
using mathtest::MathRequest;
using mathtest::MathReply;

class MathTestClient {
public:
    MathTestClient(std::shared_ptr<Channel> channel) : stub_(MathTest::NewStub(channel)) {}

    int sendRequest(int a, int b) {
        MathRequest request;

        request.set_a(a);
        request.set_b(b);

        MathReply reply;

        ClientContext context;

        Status status = stub_->sendRequest(&context, request, &reply);

        if(status.ok()){
            return reply.result();
        } else {
            std::cout << status.error_code() << ": " << status.error_message() << std::endl;
            return -1;
        }
    }

private:
    std::unique_ptr<MathTest::Stub> stub_;
};

void Run() {
    std::string address("0.0.0.0:5000");
    MathTestClient client(
            grpc::CreateChannel(
                    address,
                    grpc::InsecureChannelCredentials()
            )
    );

    int response;

    int a = 5;
    int b = 10;

    response = client.sendRequest(a, b);
    std::cout << "Answer received: " << a << " * " << b << " = " << response << std::endl;
}

int main(int argc, char* argv[]){
    Run();

    return 0;
}

The MathTestClient class provides the sendRequest() interface for sending requests to the server. sendRequest() takes two integer parameters, and then sends a request message to the server through the private member variable stub UU and receives the returned result.

The Run() function specifies the service address and initializes the MathTestClient object client, which calls the service interface and gets the return result. As you can see, when the client calls the interface of the server, it is just like calling the local interface, which is also the feature of RPC call.

Compile and run

Execute command to compile source code of client and server:

make

Execute the server program:

./server

You can see the server output:

Server listening on port: 0.0.0.0:5000

Execute client program:

./client

You can see the client output:

Answer received: 5 * 10 = 50

If an error occurs during compilation, you can refer to the following processing methods.

Solve PKG config error

The PKG config command is used in the above Makefile. If you execute make to compile the code, an error will be reported, for example:

Package protobuf was not found in the pkg-config search path.
Perhaps you should add the directory containing `protobuf.pc'
to the PKG_CONFIG_PATH environment variable
No package 'protobuf' found

If there is an error message, this is because PKG config cannot find the path of the * pc file. You can add the environment variable PKG config path to the ~ /. bashrc file:

PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH

Where / usr/local/lib/pkgconfig is the path where protobuf.pc is located (which can be found by the find command).
After adding the PKG config path environment variable, execute source ~/.bashrc, and then execute the compiled code without any error.

Solve the problem that libgrpc++.so cannot find the error

If the libgrpc++.so library file fails to find an error when running, for example:

./server: error while loading shared libraries: libgrpc++.so.1: cannot open shared object file: No such file or directory

This is because the path of the library file cannot be found by the system when gRPC is installed. It can be solved by adding ld search path.
Add the grpc.conf file in the directory / etc/ld.so.conf.d /. The content is the directory where the libgrpc++.so file is located (it can be found by the find command).

/usr/local/lib/

Then execute the command ldconfig and run the program.

Reference material

  • https://medium.com/@andrewvetovitz/grpc-c-introduction-45a66ca9461f
  • https://grpc.io/docs/quickstart/cpp/
  • https://colobu.com/2015/01/07/Protobuf-language-guide/
  • https://github.com/grpc/grpc/blob/master/BUILDING.md
188 original articles published, 437 praised, 1.22 million visitors+
His message board follow

Tags: Makefile Java Python Google

Posted on Sat, 01 Feb 2020 04:46:21 -0800 by mike97gt