~!转载请注明出处
异步传输官方示例只给了普通Unary元对象的传输,没有流式传输示例,经过摸索调试,实现了grpc的异步流式传输(目前只是单向流,服务端推流至客户端,或者客户端上送流至服务端)。
1.proto文件与上一篇同步传图一样,自然生成的demo.grpc.pb.h demo.grpc.pb.cc demo.pb.h demo.pb.cc也是一样的。
2.服务端程序
#include
#include
#include
#include
#include
#include
#include
#include
3.客户端程序
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "demo.grpc.pb.h"
#endif
using grpc::Channel;
using grpc::ClientAsyncResponseReader;
using grpc::ClientContext;
using grpc::CompletionQueue;
using grpc::Status;
using grpc::StatusCode;
using ImgTransmit::ImgDemo;
using ImgTransmit::ImgInfo;
using ImgTransmit::ImgInfo_Img;
using ImgTransmit::ImgInfo_ImgType;
typedef ImgTransmit::Status My_Status;
class ImageClientAsyncImpl {
public:
explicit ImageClientAsyncImpl(std::shared_ptr channel)
: stub_(ImgDemo::NewStub(channel))/*同一个stub_可以同时创建多个请求流*/ {}
void imgUpload(const std::vector& file_list) {
if (file_list.size()<1)
return;
AsyncImgUploadCall* call = new AsyncImgUploadCall(file_list.size(),cq_);
call->img_upload_list = file_list;
//创建一个写入流
call->writer = stub_->PrepareAsyncImgUpload(&call->context, &call->reply, &cq_);
call->writer->StartCall((void*)call);//开启写入流,一旦可写,cq_事件管理器就会检测到
}
void resImgFetched(const std::string& imgname,int count=3) {
ImgTransmit::BaseName request;
request.add_name(imgname);
AsyncResImageFetchedCall* call = new AsyncResImageFetchedCall(count);
//创建一个读取流
call->response_reader = stub_->PrepareAsyncresImgFetched(&call->context, request, &cq_);
call->response_reader->StartCall((void*)call);//开启读取流,一旦可读,cq_事件管理器就会检测到
}
void resDescFetched(const std::string& imgname) {
// Data we are sending to the server.
ImgTransmit::BaseName request;
request.add_name(imgname);
AsyncResDescFetchedCall* call = new AsyncResDescFetchedCall;
//创建一个读取流
call->response_reader =stub_->PrepareAsyncresDescFetched(&call->context, request, &cq_);
//这个接口是单个元对象传输,与流式传输有所不同。
call->response_reader->StartCall();
call->response_reader->Finish(&call->reply,&call->status, (void*)call);
}
// Loop while listening for completed responses.
void AsyncCompleteRpc() {
void* got_tag;
bool ok = false;
// Block until the next result is available in the completion queue "cq".
while (cq_.Next(&got_tag, &ok)) {
//GPR_ASSERT(ok);//Failed Maybe, Please Check Status of Network Status and Server Process
AsyncRpcCall* tmpcall = static_cast(got_tag);
if (tmpcall !=nullptr){
if (!ok) {
tmpcall->status = Status::OK;//事件异常断开,强行终止。
}
tmpcall->Proceed();
}
}
}
private:
class AsyncRpcCall {
public:
AsyncRpcCall() = default;
virtual void* GetResult() = 0;
virtual void Proceed() = 0;
virtual ~AsyncRpcCall() {}
public:
ClientContext context;
Status status= Status(StatusCode::PERMISSION_DENIED,"finished code.");
};
//普通异步接口(Unary元数据传输)
class AsyncResDescFetchedCall:public AsyncRpcCall {
public:
AsyncResDescFetchedCall(){}
void* GetResult() override {
return (void*)&reply;
}
void Proceed() override {
std::cout << "resDescFetched call, reply info: " << reply.desc()[0] << std::endl;
/**********do something else.*************/
//one-shot call for non-stream API
delete this;
return;
}
public:
ImgTransmit::Description reply;
std::unique_ptr > response_reader;
};
// 异步流式接口(单向,客户端接收服务端的流对象)
class AsyncResImageFetchedCall:public AsyncRpcCall {
public:
AsyncResImageFetchedCall(int len):times_(0), max_stream_length(len){
}
void* GetResult() override {
return (void*)(&info);
}
void Proceed() override {
if (status.ok()) {
std::cout << "rpc stream fetched ended..."< max_stream_length) {
status = Status::OK;
response_reader->Finish(&status, (void*)this);//向完成队列中加入终止读流事件
std::cout << "resImageFetched call, reply name: " << info.name() << std::endl;
std::cout << "ResImageFetchedCall end... " << std::endl;
}
else {
if (times_ > 1) {
std::cout << "resImageFetched call, reply name: " << info.name() << std::endl;
}
response_reader->Read(&info, (void*)this);//继续读取下一帧
}
}
public:
ImgTransmit::ImgInfo info;
std::unique_ptr< ::grpc::ClientAsyncReader< ImgTransmit::ImgInfo>> response_reader;
int max_stream_length;
int times_;
};
// 异步流式接口(单向,客户端推送流对象至服务端)
class AsyncImgUploadCall :public AsyncRpcCall {
public:
AsyncImgUploadCall(int len,CompletionQueue& q_): times_(0), max_stream_length(len),q(q_){}
void* GetResult() override {
return (void*)&reply;
}
void Proceed() override {
if (status.ok()) {
std::cout << "rpc stream upload ended, with final reply code: " << reply.code() << std::endl;
delete this;
}
else
{
std::cout << "rpc stream upload call. times is: "< max_stream_length) {
status = Status::OK;
writer->Finish(&status, (void*)this);//向完成队列中加入终止写流事件
}
else {
if (FillingupImginfo()){
writer->Write(info, (void*)this);//继续写入下一帧
}
else {/*
grpc::Alarm alarm;
//如果此轮不调用Write,则以this为tag向完成队列尾部写一个空task,让流保持连续
alarm.Set(&q, std::chrono::system_clock::now(), (void*)this);*/
std::cout << "fill information failed." << std::endl;
status = Status::OK;
writer->Finish(&(status), (void*)this);//直接写入终止读流事件
}
}
}
}
private:
bool FillingupImginfo() {
int id = times_ - 1;
const std::string& _path = img_upload_list[id];
std::ifstream imgreader(_path, std::ifstream::in | std::ios::binary);
long file_length = 0;
char* buffer = nullptr;
if(imgreader.is_open()){
imgreader.seekg(0, imgreader.end); //将文件流指针定位到文件末尾
file_length = imgreader.tellg();
imgreader.seekg(0, imgreader.beg); //将文件流指针重新定位到文件开头
buffer = (char*)malloc(sizeof(char) * file_length);
imgreader.read(buffer, file_length);
imgreader.close();
}
ImgInfo_Img img_detail;
if(buffer!=nullptr){
ImgInfo_ImgType type = ImgInfo_ImgType::ImgInfo_ImgType_JPG;
img_detail.set_height(224);// you can set real parameters here.
img_detail.set_width(224);
img_detail.set_channel(3);
img_detail.set_type(type);
img_detail.set_data(buffer, file_length);
free(buffer);
}
info.Clear();
info.set_name(_path);
google::protobuf::Map* maps = info.mutable_maps();
google::protobuf::MapPair item(id, img_detail);
maps->insert(item);
return true;
}
public:
My_Status reply;
ImgTransmit::ImgInfo info;
std::unique_ptr< grpc::ClientAsyncWriter< ::ImgTransmit::ImgInfo>> writer;
std::vector img_upload_list;
private:
int times_;
int max_stream_length;
CompletionQueue& q;
};
std::unique_ptr stub_;
CompletionQueue cq_;
};
int main(int argc, char** argv) {
ImageClientAsyncImpl imgclient(grpc::CreateChannel(
"localhost:50051", grpc::InsecureChannelCredentials()));
// Spawn reader thread that loops indefinitely
std::thread thread_ = std::thread(&ImageClientAsyncImpl::AsyncCompleteRpc, &imgclient);
std::string queryname("imgs/test.jpg");
imgclient.resDescFetched(queryname);
imgclient.resImgFetched(queryname);
//指定要上传的图片
std::vector lis /*= {"imgs/t1.jpg","imgs/t2.jpg"}*/;
imgclient.imgUpload(lis);
std::cout << "Press control-c to quit" << std::endl << std::endl;
thread_.join(); // blocks forever
return 0;
}
4.做一下笔记,后面有空再慢慢说吧。
流式传输与元对象传输的区别,就是流式传输需要传多个元对象才能算作一次完整的数据传输。这多个元对象可以是同步传输,也可以是异步传输。同步就是用一个循环,挨个按顺序将这些元对象发出去或者接收到,期间每个元对象的接收或发送也是阻塞的。而异步传输这些元对象时,利用了一个称之为CompletionQueue的组件,将一个元对象的发送或接收视作一次网络IO的写或读task,task需要添加到CompletionQueue中才能被处理。CompletionQueue 不允许一次性添加多个具有相同tag的task,对于同一个tag,只有前一个小task执行完了,才允许重用这个tag添加下一个task。也就是说,对于同一个tag而言(同一个tag也意味着同一个客户端请求),整个流数据的传输过程其实是同步的。之所以说是异步传输,其实是在每个小task执行期间主线程可以不阻塞。
在实际使用CompletionQueue做异步流传送时,在服务端有个现象比较奇怪,先连接的客户端如果不断有task任务被添加,将会一直优先得到处理,期间虽然其他客户端已经连接成功并添加了task,但是任务总得不到执行,也就是说这样的异步流是个伪异步。(ps猜测,可能是因为共享了tag所致, 比如最先连接的客户端向CompletionQueue中添加了一个带tag的task,当该task完成后立即又重用该tag向CompletionQueue添加新task,意味着只是更新队列中这个tag任务内容,而不是将任务追加到队列尾部,所以队头的任务会一直优先处理)。为了解决这个问题,需要借助grpc::Alarm完成,具体请参考:Lessons learnt from writing asynchronous streaming gRPC services in C++ - G Research。