目錄
Protobuf定義(包括四種模式的接口定義和所用到的簡單傳輸結構)
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源碼進行編譯