目录

Protobuf定义(包括四种模式的接口定义和所用到的简单传输结构)

Service

传输结构

单向模式

服务端函数实现

客户端函数实现

运行结果

服务端流

服务端实现

客户都安实现

运行结果

客户端流

服务端实现

客户端实现

运行结果

双向流

服务端实现

客户端实现

运行结果

踩过的一些坑


GRPC的四种模式,分别实现了单工,双工通信,客户端和服务端实现跨语言交互,试用于多种复杂通信场景。此文主要对C++实现的四种模式进行学习总结,

Protobuf定义(包括四种模式的接口定义和所用到的简单传输结构)

Service

grpc 四种类型 grpc四种模式 场景_服务端

 

传输结构

grpc 四种类型 grpc四种模式 场景_客户端_02

 

单向模式

客户端发送一个请求,阻塞等待一个回复,是最简单的一种方式,但是单向的,只能客户端触发请求。

服务端函数实现

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参数决定。

运行结果

grpc 四种类型 grpc四种模式 场景_推送_03

服务端流

服务端流,顾名思义,是一对多的情况,客户都安一个请求,对应服务端流式回复。当需要服务端主动推动数据时可以应用此模式。

服务端实现

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状态结束接收。

运行结果

grpc 四种类型 grpc四种模式 场景_grpc 四种类型_04

转存失败重新上传取消

grpc 四种类型 grpc四种模式 场景_grpc 四种类型_05

客户端流

客户端流和服务端流相似,只不过时多对一的情况,客户端流式请求,对应服务端一个回复,服务端直到接收的请求结束才返回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();服务端将知道数据传输结束,将不在读取数据,并返回状态。

运行结果

grpc 四种类型 grpc四种模式 场景_grpc 四种类型_04

转存失败重新上传取消

grpc 四种类型 grpc四种模式 场景_服务端_07

 

 

双向流

双向流时结合客户端流和服务端流的结果,服务端和客户均可主动推送数据,实现双工通信的效果。

服务端实现

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;
	}
}

由于阻塞等待接收,所以开了一个线程专门用于数据的发送,这样互相不影响发收。

运行结果

grpc 四种类型 grpc四种模式 场景_客户端_08

踩过的一些坑

  1. string类型不能传输中文字符,需要用bytes类型。
  2. proto2中message包含有继承,proto3舍弃了,所以当传输的结构有继承关系时,proto3比较麻烦。
  3.  C++下,proto文件中定义的结构set_XXX时,变量会被改成小写。
  4. Proto文件编译.cc文件中不包含预编译。

注:本文编译器使用的时VS2015,grpc库时32位,如果有其他版本需要可以下载grpc源码进行编译