文章目录
gRPC
1.介绍
在gRPC官网上,是这样介绍gRPC的:
A high performance, open source universal RPC framework
gRPC is a modern open source high performance Remote Procedure Call (RPC) 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 last mile of distributed computing to connect devices, mobile applications and browsers to backend services.
简单翻译一下,就是gRPC是一款高性能的RPC框架。而RPC是远程过程调用(Remote Procedure Call)的缩写。
RPC是分布式计算中的一个计算机通信协议,该协议允许运行于一台计算机的程序调用另一个地址空间的子程序。RPC是一种经典的CS模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。
2.优势
下面是gRPC的优势:
3.结构
gRPC使用Protobuf作为二进制序列化,使用HTTP/2进行通信。
gRPC可以简单地分为三层,包括底层的数据传输层,中间的框架层(框架层又包括C语言实现的核心功能,以及上层的编程语言框架),以及最上层由框架层自动生成的Stub
和Service
类。
Protobuf
1.介绍
在Protobuf官网上,是这样介绍Protobuf的:
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
简单来说,Protobuf是用于序列化结构化数据的中立语言,它可扩展,比起XML来说更小、更快、更简单。
2.优势
- 更小的数据量:Protobuf的二进制编码通常比XML和JSON小的多,大约3-10倍
- 更快的序列化和反序列化速度:Protobuf采用二进制格式,比XML和JSON快的多
- Protobuf是跨语言的:其支持多种编程语言,像C++, C#, Dart, Go, Java, Kotlin, Objective-C, Python, Ruby, PHP等语言都是可以的
- 易于维护可扩展:Protobuf使用
.proto
文件定义数据模型和数据格式,这种文件比XML和JSON更容易阅读和维护,且可以在不破坏原有协议的基础上轻松添加或删除字段,实现版本升级和兼容性
3.实例
在grpc/examples/protos/helloworld.proto
中的代码示例:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
使用gRPC+Protobuf
1.安装cmake
sudo apt install -y cmake
2.安装构建grpc所需要的基本工具
sudo apt install -y build-essential autoconf libtool pkg-config
3.下载源代码
git clone --recurse-submodules -b v1.60.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
4.编译安装gRPC和Protobuf
cd grpc
mkdir -p cmake/build
pushd cmake/build
cmake -DgRPC_INSTALL=ON \
-DgRPC_BUILD_TESTS=OFF \
-DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
../..
make -j 4
make install
popd
5.构建官方测试案例
# 切换到测试案例所在目录
cd examples/cpp/helloworld
# 编译测试案例
mkdir -p cmake/build
pushd cmake/build
cmake -DCMAKE_PREFIX_PATH=$MY_INSTALL_DIR ../..
make -j 8
# 运行
./greeter_server
./greeter_client
结果:
6.创建Protobuf文件
protoc -I=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` msg.proto
protoc -I=. --cpp_out=. msg.proto
msg.proto:
// msg.proto
syntax = "proto3"; // 规定使用proto3的语法
service MsgService { // 定义服务, 流数据放到括号里面
rpc GetMsg (MsgRequest) returns (MsgResponse){}
}
message MsgRequest { // 请求的结构
int32 num1 = 1;
int32 num2 = 2;
}
message MsgResponse { // 回应的结果
int32 sum = 3;
}
7.服务端
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
// protobuf生成的头文件,包含两个信息和应用的头文件
#include "msg.grpc.pb.h"
#include "msg.pb.h"
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
// 服务
class MyMsgService final : public MsgService::Service {
Status GetMsg(ServerContext* context, const MsgRequest* request,
MsgResponse* reply) override {
std::string str1("Hello");
reply->set_sum(request->num1() + request->num2()); // 给reply.sum赋值
return Status::OK;
}
};
void RunServer() {
std::string server_address("0.0.0.0:50051");
MyMsgService service;
ServerBuilder builder;
// 监听
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
// 注册服务
builder.RegisterService(&service);
// 创建server
std::unique_ptr<Server> server(builder.BuildAndStart());
// 输出
std::cout << "Server listening on " << server_address << std::endl;
// 等待输入
server->Wait();
}
int main(int argc, char** argv) {
RunServer();
return 0;
}
8.客户端
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
// protobuf生成的头文件,包含两个信息和应用的头文件
#include "msg.grpc.pb.h"
#include "msg.pb.h"
// 这是通用工具
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
class MsgServiceClient {
public:
MsgServiceClient(std::shared_ptr<Channel> channel)
: stub_(MsgService::NewStub(channel)) {}
MsgResponse GetMsg(int num1, int num2) {
// 请求数据数据格式化到request
MsgRequest request;
request.set_num1(num1);
request.set_num2(num2);
// 服务器返回端
MsgResponse reply;
//客户端上下文。它可以用来传递额外的信息
//服务器和/或调整某些RPC行为
ClientContext context;
Status status = stub_->GetMsg(&context, request, &reply);
// 如果状态是ok的话
if (status.ok()) {
return reply; // 返回和
} else {
std::cout << status.error_code() << ":" << status.error_message() << std::endl;
return reply;
}
}
private:
std::unique_ptr<MsgService::Stub> stub_;
};
int main(int argc, char** argv) {
MsgServiceClient z_msg(grpc::CreateChannel(
"localhost:50051", grpc::InsecureChannelCredentials()));
int num1,num2;
std::cout << "please input two num:" << std::endl;
std::cin >> num1 >> num2;
MsgResponse reply = z_msg.GetMsg(num1, num2);
std::cout<<reply.sum()<<std::endl;
return 0;
}
9.编译运行代码
mkdir build
cd build
cmake ..
make
10.结果
11.修改例子
如果我想把加法改为乘法,代码改动也很简单。
首先需要修改msg.proto,主要是给回应结果添加一个mul
:
// msg.proto
syntax = "proto3"; // 规定使用proto3的语法
service MsgService { // 定义服务, 流数据放到括号里面
rpc GetMsg (MsgRequest) returns (MsgResponse){}
}
message MsgRequest { // 请求的结构
int32 num1 = 2;
int32 num2 = 3;
}
message MsgResponse { // 回应的结果
int32 mul = 6;
}
然后修改服务端,主要是修改set_mul()
函数:
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
// protobuf生成的头文件,包含两个信息和应用的头文件
#include "msg.grpc.pb.h"
#include "msg.pb.h"
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
// 服务
class MyMsgService final : public MsgService::Service {
Status GetMsg(ServerContext* context, const MsgRequest* request,
MsgResponse* reply) override {
reply->set_mul(request->num1() * request->num2()); // 得到乘积
return Status::OK;
}
};
void RunServer() {
std::string server_address("0.0.0.0:50051");
MyMsgService service;
ServerBuilder builder;
// 监听
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
// 注册服务
builder.RegisterService(&service);
// 创建server
std::unique_ptr<Server> server(builder.BuildAndStart());
// 输出
std::cout << "Server listening on " << server_address << std::endl;
// 等待输入
server->Wait();
}
int main(int argc, char** argv) {
RunServer();
return 0;
}
然后修改一下客户端,主要是输出的是reply.mul
,而不是reply.sum
:
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
// protobuf生成的头文件,包含两个信息和应用的头文件
#include "msg.grpc.pb.h"
#include "msg.pb.h"
// 这是通用工具
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
class MsgServiceClient {
public:
MsgServiceClient(std::shared_ptr<Channel> channel)
: stub_(MsgService::NewStub(channel)) {}
MsgResponse GetMsg(int num1, int num2) {
// 请求数据数据格式化到request
MsgRequest request;
request.set_num1(num1);
request.set_num2(num2);
// 服务器返回端
MsgResponse reply;
//客户端上下文。它可以用来传递额外的信息
//服务器和/或调整某些RPC行为
ClientContext context;
Status status = stub_->GetMsg(&context, request, &reply);
// 如果状态是ok的话
if (status.ok()) {
return reply; // 返回和
} else {
std::cout << status.error_code() << ":" << status.error_message() << std::endl;
return reply;
}
}
private:
std::unique_ptr<MsgService::Stub> stub_;
};
int main(int argc, char** argv) {
MsgServiceClient z_msg(grpc::CreateChannel(
"localhost:50051", grpc::InsecureChannelCredentials()));
int num1,num2;
std::cout << "please input two num:" << std::endl;
std::cin >> num1 >> num2;
MsgResponse reply = z_msg.GetMsg(num1, num2);
std::cout << "mul:" << reply.mul() << std::endl;
return 0;
}
编译运行代码:
cmake ..
make
结果: