目录
1.背景
2.环境配置
3.创建.proto文件并生成对应的pb文件
4.代码实现
GRPC 是一个高性能、开源和通用的 RPC 框架,面向服务端和移动端,基于 HTTP/2 设计;
GRPC 默认使用protocol buffers,使用protocol buffers作为IDL和消息交换格式,google开源的成熟的数据序列化机制;
定义服务:通过指定方法调用的参数和返回值来定义,就是使用IDL来 描述你的服务接口和传输消息结构;
gRPC 特点:
①语言中立,支持多种语言;
②基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
③通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
④序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
简单理解:Grpc Server 实现方法,Grpc Client 像调用本地方法一样调用server的方法,Server不主动进行通信,主要依靠client调用来进行交互通信;
开发环境:Linux Qt
grpc源码地址:https://github.com/grpc/grpc/tree/v1.42.0
grpc库文件:需要自己编译或者下载已编译好的库文件即可,这里不做细致讲解。
主要讲解一下protobuf的配置,
安装包地址:https://github.com/protocolbuffers/protobuf/releases
可以根据自己需求进行瞎下载对应的安装包即可,这里我下载的时c++版本;
解压:
unzip protobuf-3.19.1.zip
进入目录:
cd protobuf-3.19.1
配置安装目录:
./config --prefix=/usr/local/protobuf
编译安装:
make
make install
建立链接到bin下:
ln -sf /usr/local/protobuf/bin/protoc /usr/bin/protoc
查看版本号:
protoc --version
proto编写:
syntax = "proto3";
package MyGrpcS; //类似于c++ 命名空间
//定义请求信息
message SearchARequest
{
//参数类型 名称 标识号
string request = 1;
int32 statusr = 2;
}
//定义响应信息
message SearchAResponse
{
//参数类型 名称 标识号
string response = 3;
int32 statusr = 4;
}
//同上
message SearchBRequest
{
string request = 5;
int32 statusr = 6;
}
message SearchBResponse
{
string response = 7;
int32 statusr = 8;
}
message StreamRequest
{
int32 mode = 9;
}
message StreamResponse
{
string data = 10;
string type = 11;
int32 num = 12;
}
service SearchService{
//sample rpc
rpc getDataA(SearchARequest) returns (SearchAResponse){}
rpc setDataA(SearchAResponse) returns (SearchARequest){}
rpc getDataB(SearchBRequest) returns (SearchBResponse){}
rpc setDataB(SearchBResponse) returns (SearchBRequest){}
//server rpc
rpc streamData(StreamRequest) returns (stream StreamResponse){}
//client rpc
rpc ctreamData(stream StreamRequest) returns (StreamResponse){}
//server and client rpc
rpc scstreamData(stream StreamRequest) returns (stream StreamResponse){}
}
编译:
protoc --cpp_out=./ GrpcServer.proto
//将生成GrpcServer.pb.cc GrpcServer.pb.h文件
protoc --grpc_out=./ --plugin=protoc-gen-grpc=/home/jiang/ProgramQt/libs/grpc/grpc_cpp_plugin GrpcServer.proto
//将生成GrpcServer.grpc.pb.h GrpcServer.grpc.pb.cc文件
文件如下:
server端通信需要实现的:
//运行服务器
void runServer(){
std::string server_addr("0.0.0.0:50051");
GrpcServerImp1 service;
ServerBuilder builder;
builder.AddListeningPort(server_addr,grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr server(builder.BuildAndStart());
std::cout<<"server listening on"<Wait();
}
//重载service的虚函数
class GrpcServerImp1 final:public SearchService::Service
{
public:
::grpc::Status getDataA(::grpc::ServerContext* context, const ::MyGrpcS::SearchARequest* request, ::MyGrpcS::SearchAResponse* response)override;
::grpc::Status setDataA(::grpc::ServerContext* context, const ::MyGrpcS::SearchAResponse* request, ::MyGrpcS::SearchARequest* response)override;
::grpc::Status getDataB(::grpc::ServerContext* context, const ::MyGrpcS::SearchBRequest* request, ::MyGrpcS::SearchBResponse* response)override;
::grpc::Status setDataB(::grpc::ServerContext* context, const ::MyGrpcS::SearchBResponse* request, ::MyGrpcS::SearchBRequest* response)override;
::grpc::Status streamData(::grpc::ServerContext* context, const ::MyGrpcS::StreamRequest* request, ::grpc::ServerWriter< ::MyGrpcS::StreamResponse>* writer)override;
::grpc::Status ctreamData(::grpc::ServerContext* context, ::grpc::ServerReader< ::MyGrpcS::StreamRequest>* reader, ::MyGrpcS::StreamResponse* response)override;
::grpc::Status scstreamData(::grpc::ServerContext* context, ::grpc::ServerReaderWriter< ::MyGrpcS::StreamResponse, ::MyGrpcS::StreamRequest>* stream)override;
};
注意:建议虚函数直接跳转到service类下进行拷贝,避免无法识别,之前就因为是自己手动敲的,一直通信都不成功,报错UNIMPLEMENTED(service 方法找不到),找了很久才解决,也不知道是不是编译器的问题还是什么问题就很奇怪。
client需要实现的:
//定义类对象
class ExmapleClint{
public:
explicit ExmapleClint(std::shared_ptr channel):
stub_(SearchService::NewStub(channel)){}
std::unique_ptr stub_;
};
//创建对象
ChannelArguments args;
m_pClient = new ExmapleClint(grpc::CreateCustomChannel("127.0.0.1:50051", grpc::InsecureChannelCredentials(),args));
grpc有四种交互模式,如下:
①简单模式(Simple RPC),客户端发起请求并等待服务端响应;
//sample rpc
rpc getDataA(SearchARequest) returns (SearchAResponse){}
rpc setDataA(SearchAResponse) returns (SearchARequest){}
rpc getDataB(SearchBRequest) returns (SearchBResponse){}
rpc setDataB(SearchBResponse) returns (SearchBRequest){}
server 代码如下:
grpc::Status GrpcServerImp1::getDataA(grpc::ServerContext *context, const SearchARequest *request, SearchAResponse *response)
{
response->set_statusr(5);
response->set_response(m_sSetA);
return Status::OK;
}
grpc::Status GrpcServerImp1::setDataA(grpc::ServerContext *context, const SearchAResponse *request, SearchARequest *response)
{
int status = response->statusr();
m_sGetA = request->response();
m_sGetA += "status:"+to_string(status);
return Status::OK;
}
grpc::Status GrpcServerImp1::getDataB(grpc::ServerContext *context, const SearchBRequest *request, SearchBResponse *response)
{
response->set_statusr(10);
response->set_response(m_sSetB);
return Status::OK;
}
grpc::Status GrpcServerImp1::setDataB(grpc::ServerContext *context, const SearchBResponse *request, SearchBRequest *response)
{
int status = response->statusr();
m_sGetB = request->response();
m_sGetB += "status:"+to_string(status);
return Status::OK;
}
client代码如下:(这里我是在Qt的槽函数中实现的)
connect(m_pSetDA_But,&QPushButton::clicked,this,[&](){
MyGrpcS::SearchARequest request;
MyGrpcS::SearchAResponse response;
ClientContext context;
response.set_response(m_pSetDA_Edit->toPlainText().toStdString());
response.set_statusr(1);
Status status = m_pClient->stub_->setDataA(&context,response,&request);
if(status.ok())
{
this->statusBar()->showMessage("set data A success");
}
else
{
cout<statusBar()->showMessage("RPC FAIL");
}
});
connect(m_pGetDA_But,&QPushButton::clicked,this,[&](){
MyGrpcS::SearchARequest request;
MyGrpcS::SearchAResponse response;
ClientContext context;
request.set_request("A ");
request.set_statusr(1);
Status status = m_pClient->stub_->getDataA(&context,request,&response);
if(status.ok())
{
string param1 = response.response();
int param2 = response.statusr();
m_pGetDA_Brower->setText(QString("Data A:%1 %2").arg(param1.c_str()).arg(param2));
}
else
{
cout<setText(QString("rpc failed"));
}
});
connect(m_pSetDB_But,&QPushButton::clicked,this,[&](){
MyGrpcS::SearchBRequest request;
MyGrpcS::SearchBResponse response;
ClientContext context;
response.set_response(m_pSetDB_Edit->toPlainText().toStdString());
response.set_statusr(1);
Status status = m_pClient->stub_->setDataB(&context,response,&request);
if(status.ok())
{
this->statusBar()->showMessage("set data B success");
}
else
{
cout<statusBar()->showMessage("RPC FAIL");
}
});
connect(m_pGetDB_But,&QPushButton::clicked,this,[&](){
MyGrpcS::SearchBRequest request;
MyGrpcS::SearchBResponse response;
ClientContext context;
request.set_request("B ");
request.set_statusr(1);
Status status = m_pClient->stub_->getDataB(&context,request,&response);
if(status.ok())
{
string param1 = response.response();
int param2 = response.statusr();
m_pGetDB_Brower->setText(QString("Data B:%1 %2").arg(param1.c_str()).arg(param2));
}
else
{
cout<setText(QString("rpc failed"));
}
});
client 主要是通过stub_去调用server重载的方法来进行请求和响应的。
②服务端流式 RPC(Server-side streaming RPC),客户端发起一个请求到服务端,服务端返回一段连续的数据流响应;
//server rpc
rpc streamData(StreamRequest) returns (stream StreamResponse){}
server实现:
grpc::Status GrpcServerImp1::streamData(grpc::ServerContext *context, const StreamRequest *request, ::grpc::ServerWriter *writer)
{
cout<<"enter streamdata"< *stream = new GrpcStream;
stream->isclosed = 0;
stream->request = (StreamRequest*)request;
stream->writer = writer;
//listStreams->push_back(stream); //save once time request
//这里我手动简单的进行循环响应,实际开发中可以设置一个线程进行不断write
for(int i=1;i<6;i++)
{
MyGrpcS::StreamResponse *response = new MyGrpcS::StreamResponse;
response->set_num(i);
response->set_data("ssssssssssss"+to_string(i));
response->set_type("server-rpc");
::grpc::WriteOptions options;
stream->writer->Write(*response,options);
delete response;
response = nullptr;
}
delete stream;
stream = nullptr;
return Status::OK;
}
client实现:
connect(m_pStreamBut,&QPushButton::clicked,this,[&](){
ClientContext context;
MyGrpcS::StreamRequest request;
request.set_mode(1);
std::unique_ptr> reader = m_pClient->stub_->streamData(&context,request);
MyGrpcS::StreamResponse response;
int count = 0;
while(1) //客户端不断的去read 直到没有数据就推出循环(开发中可以加线程进行不断的read)
{
if(reader->Read(&response))
{
string data = response.data();
string type= response.type();
int num = response.num();
QString cc = QString("data:%1 ").arg(data.c_str());
QString tt = QString("type:%1 ").arg(type.c_str());
QString ss = QString("num:%1 ").arg(num);
if(m_pStream_Brower)
{
m_pStream_Brower->insertPlainText(cc);
m_pStream_Brower->insertPlainText(tt);
m_pStream_Brower->insertPlainText(ss);
QApplication::processEvents();//因为槽函数也是线程,会导致界面不能刷新,因此使用线程事件刷新
}
count = 0;
cout<<"read success"<
③客户端流式 RPC(Client-side streaming RPC),与服务端流式相反,客户端流式是客户端不断地向服务端发送数据流,最后由服务端返回一个响应;
//client rpc
rpc ctreamData(stream StreamRequest) returns (StreamResponse){}
server实现:
grpc::Status GrpcServerImp1::ctreamData(grpc::ServerContext *context, ::grpc::ServerReader *reader, StreamResponse *response)
{
//client rpc
cout<<"client rpc"< *stream = new GrpcStream;
stream->isclosed = 0;
stream->response = response;
stream->reader = reader;
StreamRequest request;
while(reader->Read(&request)) //如果客户端不执行writedone,就会出现阻塞在此处,也不会退出while循环
{
cout<<"mode:"<set_num(1);
response->set_data("server recv cilent request...");
response->set_type("client-rpc");
return Status::OK;
}
client实现:
connect(m_pCStreamBut,&QPushButton::clicked,this,[&](){
ClientContext context;
MyGrpcS::StreamRequest request;
MyGrpcS::StreamResponse response;
std::unique_ptr> writer = m_pClient->stub_->ctreamData(&context,&response);
grpc::WriteOptions args;
int count = 1;
bool status = false;
//send request
while(1)
{
request.set_mode(count);
if(writer->Write(request,args)) //写入数据
{
}
else
break;
if(count >6)
break;
++count;
}
writer->WritesDone();//写入完成 如果不执行此函数会导致 server 出现read一直不结束会阻塞在那儿
Status st = writer->Finish();
if(!st.ok())
cout<<"status error:"<insertPlainText(cc);
m_pCStream_Brower->insertPlainText(tt);
m_pCStream_Brower->insertPlainText(ss);
QApplication::processEvents();//同理,刷新线程
}
}
cout<<"exit thread"<
④双向流式 RPC(Bidirectional streaming RPC),客户端和服务端可同时向对方发送数据流,同时也可以接收数据;
//server and client rpc
rpc scstreamData(stream StreamRequest) returns (stream StreamResponse){}
server实现:
grpc::Status GrpcServerImp1::scstreamData(grpc::ServerContext *context, ::grpc::ServerReaderWriter *stream)
{
cout<<"server and client rpc"<Read(&request)) //也需要client运行writedone()才会结束
{
cout<<"mode:"<Write(response,options);
i++;
if(status)
break;
}
return Status::OK;
}
client实现:
connect(m_pSCStreamBut,&QPushButton::clicked,this,[&](){
ClientContext context;
MyGrpcS::StreamRequest request;
MyGrpcS::StreamResponse response;
std::unique_ptr< ::grpc::ClientReaderWriter< ::MyGrpcS::StreamRequest, ::MyGrpcS::StreamResponse>> wRriter = m_pClient->stub_->scstreamData(&context);
grpc::WriteOptions args;
int count = 1;
bool status = false;
//send request //此处可以使用线程同时执行
while(1)
{
request.set_mode(count);
if(wRriter->Write(request,args))
{
}
else
{
break;
}
if(count >6)
break;
++count;
}
wRriter->WritesDone(); //写入结束 避免server read 阻塞
count =1;
status = false;
//get response
while(wRriter->Read(&response)) //一直读取响应的数据
{
string data = response.data();
string type= response.type();
int num = response.num();
QString cc = QString("data:%1 ").arg(data.c_str());
QString tt = QString("type:%1 ").arg(type.c_str());
QString ss = QString("num:%1 ").arg(num);
if(m_pSCStream_Brower)
{
m_pSCStream_Brower->insertPlainText(cc);
m_pSCStream_Brower->insertPlainText(tt);
m_pSCStream_Brower->insertPlainText(ss);
QApplication::processEvents();//while cause thread make not plush ui
}
}
Status st = wRriter->Finish();
if(!st.ok())
cout<<"error:"<
以上四种方式就简单实现了,实际开发中很多都是使用线程处理实时请求和响应数据。
原理:
A.grpc使用.proto生成client和 server代码及API,Client调用生成的API,server实现这些API.
B.serve:实现服务声明的方法,并运行grpc server来处理client的调用,grpc帮助反序列请求,执行服务方法,序列化响应并返回个client.
C.client:根据grpc生成的client,这个client提供和server一样的接口供用户调用,并且使用protocol buffer序列化用户的请求参数,发送个 server,然后反序列化server的响应.
四种方式rpc就简单实现了,grpc的原理也基本搞懂,能够实际运用到开发中。至于底层原理等有时间再去分析grpc源码。
参考:https://blog.csdn.net/asd1126163471/article/details/117794401
https://www.jianshu.com/p/9e57da13b737
https://www.cnblogs.com/MakeView660/p/11512346.html