目录
Protobuf定义(包括四种模式的接口定义和所用到的简单传输结构)
Service
传输结构
单向模式
服务端函数实现
客户端函数实现
运行结果
服务端流
服务端实现
客户都安实现
运行结果
客户端流
服务端实现
客户端实现
运行结果
双向流
服务端实现
客户端实现
运行结果
踩过的一些坑
GRPC的四种模式,分别实现了单工,双工通信,客户端和服务端实现跨语言交互,试用于多种复杂通信场景。此文主要对C++实现的四种模式进行学习总结,
Protobuf定义(包括四种模式的接口定义和所用到的简单传输结构)
Service
传输结构
单向模式
客户端发送一个请求,阻塞等待一个回复,是最简单的一种方式,但是单向的,只能客户端触发请求。
服务端函数实现
Status ZZXG_Server::RequestMsg(ServerContext* context, const RequestInfo* request, ReplyInfo* reply)
{
switch (request->askmsg())
{
case TransMsg::AskType::name:
reply->set_remsg("粽子小哥");
break;
case TransMsg::AskType::age:
reply->set_remsg(std::to_string(25));
break;
case TransMsg::AskType::nativeplace:
reply->set_remsg("四川");
break;
default:
reply->set_remsg("未知请求");
break;
}
return Status::OK;
}
收到请求后,给返回变量赋值,return Status::OK即为赋值完毕。
客户端函数实现
void ZZXG_Client::RequestMsgTest()
{
// 定义请求对象,并赋值.
RequestInfo request;
request.set_askmsg(TransMsg::name);
// 返回对象定义.
ReplyInfo reply;
ClientContext context;
// 调用请求函数.
Status status = stub_->RequestMsg(&context, request, &reply);
// 返回状态.
if (status.ok())
{
std::cout << "接收到服务端回复: " << reply.remsg() << std::endl;
}
else
{
std::cout << "单向 rpc failed." << std::endl;
}
}
Status status = stub_->RequestMsg(&context, request, &reply);会等待服务端对reply赋值返回status为止,若返回状态不是ok,则通信出错,或者数据传输出错,具体根据返回的status参数决定。
运行结果
服务端流
服务端流,顾名思义,是一对多的情况,客户都安一个请求,对应服务端流式回复。当需要服务端主动推动数据时可以应用此模式。
服务端实现
Status ZZXG_Server::RequestMsgServerStream(ServerContext* context, const RequestInfo* request, ServerWriter<ReplyInfo>* writer)
{
//此处只有初始化的5组测试数据,实际根据需要推送的数据进行回复,可以一直阻塞推送
int i = 0;
std::string str = "";
for (ReplyInfo reply : replyInfoList)
{
switch (request->askmsg())
{
case TransMsg::AskType::name:
str = "name";
reply.set_remsg("粽子小哥"+ std::to_string(i));
break;
case TransMsg::AskType::age:
str = "age";
reply.set_remsg(std::to_string(25));
break;
case TransMsg::AskType::nativeplace:
str = "native place";
reply.set_remsg("四川");
break;
default:
str = "未知请求";
reply.set_remsg("未知请求");
break;
}
writer->Write(reply);
i++;
}
std::cout << "接受到客户端的请求:" << str << "\n" << std::endl;
return Status::OK;
}
接收到客户端请求开始,服务端组织参数回复,直到return Status::OK;可以在此期间wirter调用write函数进行回复写入,进行多次调用,达到服务端主动推送的效果。
客户都安实现
void ZZXG_Client::RequestMsgServerStreamTest()
{
RequestInfo requestInfo;
requestInfo.set_askmsg(TransMsg::name);
ClientContext context;
ReplyInfo replyInfo;
std::unique_ptr<ClientReader<ReplyInfo> > reader(stub_->RequestMsgServerStream(&context, requestInfo));
while (reader->Read(&replyInfo))
{
std::cout << "收到服务端的回复: "<< replyInfo.remsg() << " --\n "<< std::endl;
}
Status status = reader->Finish();
if (!status.ok())
{
std::cout << "服务端流 rpc failed." << std::endl;
}
}
客户端发出请求后,循环进入等待接收,通过reader调用read函数进行读取,直到服务端返回ok状态结束接收。
运行结果
转存失败重新上传取消
客户端流
客户端流和服务端流相似,只不过时多对一的情况,客户端流式请求,对应服务端一个回复,服务端直到接收的请求结束才返回status::ok。应用于客户端主动流式给服务端推送数据的场景。
服务端实现
Status ZZXG_Server::RequestMsgClientStream(ServerContext* context, ServerReader<RequestInfo>* reader, ReplyInfo* reply)
{
RequestInfo request;
std::string strInfo;
std::string str;
while (reader->Read(&request))
{
switch (request.askmsg())
{
case TransMsg::AskType::name:
str += "-name-";
strInfo += "粽子小哥";
break;
case TransMsg::AskType::age:
str += "-age-";
strInfo += std::to_string(25);
break;
case TransMsg::AskType::nativeplace:
str += "-native place-";
strInfo += "四川";
break;
}
}
std::cout << "接受到客户端的请求:" << str << "\n" << std::endl;
reply->set_remsg(strInfo);
return Status::OK;
}
客户端实现
void ZZXG_Client::RequestMsgClientStreamTest()
{
ReplyInfo reply;
ClientContext context;
std::unique_ptr<ClientWriter<RequestInfo> > writer(stub_->RequestMsgClientStream(&context, &reply));
for (RequestInfo request:_requestList)
{
writer->Write(request);
}
writer->WritesDone();
Status status = writer->Finish();
if (status.ok()) {
std::cout << "接收到服务端回复: " << reply.remsg() << " --\n"<< std::endl;
}
else {
std::cout << "客户端流 rpc failed." << std::endl;
}
}
当客户端writer->WritesDone();服务端将知道数据传输结束,将不在读取数据,并返回状态。
运行结果
转存失败重新上传取消
双向流
双向流时结合客户端流和服务端流的结果,服务端和客户均可主动推送数据,实现双工通信的效果。
服务端实现
Status ZZXG_Server::RequestMsg2WayStream(ServerContext* context, ServerReaderWriter<ReplyInfo, RequestInfo>* stream)
{
RequestInfo request;
while (stream->Read(&request))
{
std::cout << "收到请求,类型为" << request.askmsg() <<"\n"<<std::endl;
int i = 0;
for (ReplyInfo reply : replyInfoList)
{
switch (request.askmsg())
{
case TransMsg::AskType::name:
reply.set_remsg("粽子小哥" + std::to_string(i));
break;
case TransMsg::AskType::age:
reply.set_remsg(std::to_string(25));
break;
case TransMsg::AskType::nativeplace:
reply.set_remsg("四川");
break;
default:
reply.set_remsg("未知请求");
break;
}
stream->Write(reply);
i++;
}
}
客户端实现
//发送数据到服务端
void ZZXG_Client::SendRequestToServer(std::shared_ptr<ClientReaderWriter<RequestInfo, ReplyInfo> > writer)
{
for (RequestInfo request : _requestList)
{
writer->Write(request);
}
writer->WritesDone();
}
void ZZXG_Client::RequestMsg2WayStreamTest()
{
ClientContext context;
std::shared_ptr<ClientReaderWriter<RequestInfo, ReplyInfo> > writerRead(stub_->RequestMsg2WayStream(&context));
writerThread = new std::thread(&ZZXG_Client::SendRequestToServer, this, writerRead);
ReplyInfo reply;
while (writerRead->Read(&reply)) {
std::cout << "接收到回复:" << reply.remsg()<<"--\n" << std::endl;
}
writerThread->join();
Status status = writerRead->Finish();
if (!status.ok())
{
std::cout << "双向流 rpc failed." << std::endl;
}
}
由于阻塞等待接收,所以开了一个线程专门用于数据的发送,这样互相不影响发收。
运行结果
踩过的一些坑
- string类型不能传输中文字符,需要用bytes类型。
- proto2中message包含有继承,proto3舍弃了,所以当传输的结构有继承关系时,proto3比较麻烦。
- C++下,proto文件中定义的结构set_XXX时,变量会被改成小写。
- Proto文件编译.cc文件中不包含预编译。
注:本文编译器使用的时VS2015,grpc库时32位,如果有其他版本需要可以下载grpc源码进行编译