之前曾经写过 Python 使用 gRPC 收发消息的教程,可以参考文章 《体验 gRPC 那些事儿》。最近计划在 C++ 项目中使用 gRPC,故写一篇文章来记录一下如何使用 C++ 语言来实现一个简单的 gRPC 服务端和客户端程序。
本教程需要先安装 gRPC,有关 gRPC 的安装教程可以参考文章 《CentOS 7 安装 gRPC》,《体验 gRPC 那些事儿》。
本文涉及的程序包括四部分,分别是客户端源代码 client.cc
,服务端源代码 server.cc
,proto 文件 mathtest.proto
以及 Makefile。
Makefile
Makefile 的作用是编译源代码,Makefile 文件如下:
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
执行这个 Makefile 文件,会生成可执行客户端 client
和服务端 server
可执行文件。
客户端和服务端可执行文件都依赖于由 proto 文件生成的源代码,Makefile 在 clean
前面的两个规则的作用即是生成这些代码。
Makefile 文件中出现了一些特殊符号:
- $@:规则的目标名称,例如上面 Makefile 中代表
client
和server
- %:匹配字符,例如上面 Makefile 中匹配
mathtest.proto
- $<:依赖的第一个目标名称,例如上面 Makefile 中的
mathtest.proto
- $^:依赖的所有目标名称,上面 Makefile 中的
server
规则中,即代表mathtest.pb.o mathtest.grpc.pb.o server.o
有关 Makefile 的解析,可以参考阮一峰的 《Make 命令教程》。
Proto
.proto
文件用来定义客户端和服务端的消息格式。
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
定义了使用 proto3 的语法(还有 proto2)java_package
用来定义生成 Java 类所在的包package
表示定义消息的包名,对于 C++ 程序,消息类将会被包装在对应的命名空间中,例如,使用mathtest::MathRequest
来引用MathRequest
消息service
用来定义在 RPC 调用中的服务接口,这里我们定义了一个服务接口sendRequest
,它接受一个MathRequest
消息,并返回MathReply
消息message
用来定义消息的字段,例如,MathRequest
消息包含两个整型字段a
和b
,MathReply
包含一个整型字段result
。字段后面的数字并不表示该字段的值,只是proto
程序用来生成相关源代码使用。
有关 Protocol Buffers 的介绍,可以参考官方文档,https://developers.google.com/protocol-buffers/docs/overview。
服务端
服务端代码 server.cc
如下:
//
// 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;
}
在代码开头,我们引入了 grpc 头文件以及 proto 生成的头文件,然后我们声明了在服务端代码中使用的相应命名空间下的类型。
MathServiceImplementation
实现服务接口 sendRequest()
,该接口获取请求消息中的 a
和 b
字段的值,并返回 a * b
的积。MathServiceImplementation
是个实现类,继承于 MathTest::Service
,MathTest::Service
在 mathtest.grpc.pb.h
声明。
Run()
函数指定了服务地址 0.0.0.0:5000
,对 MathServiceImplementation
服务注册后,我们启动了服务端,并接受客户端的请求。
客户端
客户端 client.cc
代码如下:
//
// 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;
}
MathTestClient
类提供向服务端发送请求的接口 sendRequest()
。sendRequest()
接受两个整型参数,然后通过私有成员变量 stub_
向服务端发送请求消息并接收返回结果。
Run()
函数指定服务地址并初始化 MathTestClient
对象 client
,通过对象 client
调用服务接口并获取返回结果。可以看到,在客户端调用服务端的接口时,就像是调用本地接口一样,这也是 RPC 调用的特色。
编译运行
执行命令编译客户端和服务端源代码:
make
执行服务端程序:
./server
可以看到服务端输出:
Server listening on port: 0.0.0.0:5000
执行客户端程序:
./client
可以看到客户端输出:
Answer received: 5 * 10 = 50
如果执行编译过程时报错,可以参考以下处理方法。
解决 pkg-config 报错
上述 Makefile 中使用了 pkg-config
命令,如果执行 make
编译代码时报错,例如:
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
如报错提示,这是由于 pkg-config 无法搜到 *pc 文件所在路径导致,可以通过在 ~/.bashrc 文件添加环境变量 PKG_CONFIG_PATH
:
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
其中,/usr/local/lib/pkgconfig
为 protobuf.pc
所在路径(可以通过 find
命令搜索到)。
添加 PKG_CONFIG_PATH
环境变量后,执行 source ~/.bashrc
,再执行编译代码,不再报错。
解决 libgrpc++.so 无法找到报错
如果运行时报 libgrpc++.so 库文件无法找到的错误,例如:
./server: error while loading shared libraries: libgrpc++.so.1: cannot open shared object file: No such file or directory
这是由于 gRPC 安装时库文件所在的路径无法被系统找到导致。可以通过增加 ld 搜索路径解决。
在 /etc/ld.so.conf.d/
目录增加 grpc.conf
文件,内容是 libgrpc++.so 文件所在的目录(可以通过 find
命令搜索到)。
/usr/local/lib/
然后执行命令 ldconfig
,再运行程序即可。
参考资料
- 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
来源:CSDN
作者:haozlee
链接:https://blog.csdn.net/lihao21/article/details/104138126