c++ grpc 实现一个传图服务(同步方式,流式传输)

        主要参考hollowworld以及route_guide程序示例。同步方式由于官方给的例子里面有,所以相对容易。为了增加点难度顺便学习一下protobuf,所以接口定义文件中用了比较复杂的字典和枚举数据结构。

1. proto文件:

// image transmission server
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.ImgTransmit";
option java_outer_classname = "ImgTransmit";
option objc_class_prefix = "IMT";

package ImgTransmit;

// service definition.
service ImgDemo {
  //simple RPC
  rpc resDescFetched(BaseName) returns (Description) {}
  // A client-to-server streaming RPC. 
  // stream type means a group of ImgInfo will be sent orderly from client
  rpc ImgUpload (stream ImgInfo) returns (Status) {}
  // A server-to-client streaming RPC. send result img to client
  rpc resImgFetched (BaseName) returns (stream ImgInfo) {}
}

message ImgInfo {
  
  string name = 1;
  enum ImgType {
    JPG = 0;
    PNG = 1;
  }
  message Img {
   bytes data = 1;
   ImgType type = 2;
   int32 height=3;
   int32 width=4;
   int32 channel=5;
  }
  // int32 indicates original img id 
  map<int32,Img> maps = 2;
}

message Status {
  int32 code = 1;
}

message BaseName {
	repeated string name=1;
}
message Description {
	repeated string desc=1;
}

由proto文件生成demo.grpc.pb.h demo.grpc.pb.cc demo.pb.h demo.pb.cc文件就不放了,可以用grpc提供的工具以及protobuf工具自行生成。

2. server服务端程序

#include <algorithm>
#include <chrono>
#include <cmath>
#include <iostream>
#include <fstream>
#include <memory>
#include <string>
#include<map>
#include <exception> 

#include <grpcpp/grpcpp.h>
#include "demo.grpc.pb.h"
#include "demo.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using ImgTransmit::ImgInfo;
using ImgTransmit::ImgInfo_Img;
typedef  ImgTransmit::Status My_Status;
using ImgTransmit::ImgDemo;


using Ms = std::chrono::milliseconds;
using Sec = std::chrono::seconds;
template <class UnitType=Ms>
using TimePoint = std::chrono::time_point<std::chrono::high_resolution_clock, UnitType>;
/*
		客户端,直接操作stub客户存根类对象进行通讯
		服务端,继承ImgDemo::Service,并实现相应的接口

		本例客户端,服务端均是同步(sync)实现
*/
// Logic and data behind the server's behavior.
class ImageServiceImpl final : public ImgDemo::Service {
public:
	::grpc::Status ImgUpload(ServerContext* context, ::grpc::ServerReader< ::ImgTransmit::ImgInfo>* reader, My_Status* response) override {
		
		::ImgTransmit::ImgInfo info;
		int point_count = 0;
		int feature_count = 0;
		float distance = 0.0;
		// reader 接收客户端传来的一组图片
		if (resultList.size() > 0)
			resultList.clear();
		int error = -1;
		int count = 0;
		std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now();
		while (reader->Read(&info)) {
			//挨个处理图片
			try {
				std::string name = info.name();
				google::protobuf::Map<google::protobuf::int32, ImgInfo_Img> maps = info.maps();
				int size = maps.size();
				google::protobuf::Map<google::protobuf::int32, ImgInfo_Img>::iterator it = maps.begin();
				for (; it != maps.end(); it++) {
					ImgInfo_Img img = it->second;
					int c = img.channel();
					int h = img.height();
					int w = img.width();
					std::string rowData = img.data();
					std::cout << "img size :" << h << "," << w << "," << c<<", data length:"<< rowData.size()<< std::endl;
					resultList[name] = info;
					/*//可将收到的图片保存到指定目录
					std::string savePath("/img/img-server/recevied_img/");
					std::ofstream  out(savePath+name, std::ios::out | std::ios::binary | std::ios::ate);
					out.write(rowData.c_str(), sizeof(char) * (rowData.size()));
					out.close();
					*/
				}
				count++;

			}
			catch (std::exception& e) {
				std::cout << "exception: " << e.what() << std::endl;
				error += 1;
			}
		}
		//告知客户端,消息是否收到
		std::chrono::system_clock::time_point end_time = std::chrono::system_clock::now();
		Sec secs =std::chrono::duration_cast<Sec>(end_time - start_time);
		Ms ms = std::chrono::duration_cast<Ms>(end_time - start_time);
		TimePoint<Sec> sec_time_point(secs);
		TimePoint<Ms> ms_time_point(ms);
		std::cout << count <<" images received success."<<std::endl;
		std::cout << "time cost is: "<< ms_time_point.time_since_epoch().count() <<" ms,  "
			<< sec_time_point.time_since_epoch().count()<<" seconds"<<std::endl;
		response->set_code(1);
		if (error > 0) {
			return ::grpc::Status(::grpc::StatusCode::DATA_LOSS, "data received uncompletely.");
		}
		else
			return ::grpc::Status::OK;
	}

	::grpc::Status resImgFetched(::grpc::ServerContext* context, const ::ImgTransmit::BaseName* request, ::grpc::ServerWriter< ::ImgTransmit::ImgInfo>* writer)override {
		//const std::string& name = request->name();
		const google::protobuf::RepeatedPtrField<std::string>& names=request->name();
		int count=0, size = names.size();
		for (int i = 0; i < size;i++) {
			const std::string& singleName = names.Get(i);
			if (resultList.count(singleName) == 1) {
						::ImgTransmit::ImgInfo res = resultList[singleName];
						writer->Write(res);
						count--;
			}
			count++;
		}
		if(count<size)
			return ::grpc::Status::OK;
		else 
			return ::grpc::Status(::grpc::StatusCode::NOT_FOUND, "uncompleted reponse.");
	}

	::grpc::Status resDescFetched(::grpc::ServerContext* context, const ::ImgTransmit::BaseName* request, ::ImgTransmit::Description* response)override {
		const google::protobuf::RepeatedPtrField<std::string>& names = request->name();
		const google::protobuf::RepeatedPtrField<std::string>* rsp = response->mutable_desc();
		int count = 0, size = names.size();
		for (int i = 0; i < size; i++) {
			const std::string& singleName = names.Get(i);
			if (resultList.count(singleName) == 1) {
				std::string ss;
				const ::ImgTransmit::ImgInfo& singleInfo = resultList[singleName];
				response->add_desc(singleInfo.name());
				count--;
			}
			count++;
		}
		if (count < size)
			return ::grpc::Status::OK;
		else
			return ::grpc::Status(::grpc::StatusCode::NOT_FOUND, "uncompleted reponse.");
	}
private:
	std::map<std::string,::ImgTransmit::ImgInfo> resultList;
};

void RunServer() {
	std::string server_address("0.0.0.0:50057");
	ImageServiceImpl service;
	
	ServerBuilder builder;
    //Server-side Connection Management
    builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);//默认为0,在没有rpc待处理的情况下,不允许发PING帧
	builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_TIME_MS,10000);//默认7200000,两个小时后发送PING帧
	builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 10000);//默认20000,20秒后如果没收到PING ACK,就重发PING
	builder.AddChannelArgument(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA, 10);//默认累计发了2个PING帧之后,必须发送一次带数据的帧才能继续发PING帧
	builder.AddChannelArgument(GRPC_ARG_HTTP2_MAX_PING_STRIKES, 5);//默认为2,最多重发2次如果对方不响应,就断开连接
	// Listen on the given address without any authentication mechanism.
	builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
	// Register "service" as the instance through which we'll communicate with
	// clients. In this case it corresponds to an *synchronous* service.
	builder.RegisterService(&service);
	// Finally assemble the server.
	std::unique_ptr<Server> server(builder.BuildAndStart());
	std::cout << "Server listening on " << server_address << std::endl;

	// Wait for the server to shutdown. Note that some other thread must be
	// responsible for shutting down the server for this call to ever return.
	server->Wait();
}


int main(int argc, char** argv) {

	std::cout << "ready go to sleeping" << std::endl;
	RunServer();

	return 0;
}

3.client 客户端程序

#include <chrono>
#include <iostream>
#include <fstream>
#include <memory>
#include <random>
#include <string>
#include <thread>
#include <vector>

#include <grpc/grpc.h>
#include <grpcpp/channel.h>
#include <grpcpp/client_context.h>
#include <grpcpp/create_channel.h>
#include <grpcpp/security/credentials.h>
#include "demo.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::ClientReader;
using grpc::ClientReaderWriter;
using grpc::ClientWriter;
using grpc::Status;
using ImgTransmit::ImgInfo_Img;
using ImgTransmit::ImgInfo_ImgType;
using ImgTransmit::ImgInfo;
using ImgTransmit::BaseName;
using ImgTransmit::Description;
typedef  ImgTransmit::Status My_Status;
using ImgTransmit::ImgDemo;


using Ms = std::chrono::milliseconds;
using Sec = std::chrono::seconds;
template <class UnitType = Ms>
using TimePoint = std::chrono::time_point<std::chrono::high_resolution_clock, UnitType>;

void parse_txt(const std::string & txt_path,std::vector<std::string>& content) {
    std::ifstream fp(txt_path);
    if (!fp.is_open())
    {
        std::cout << "can not open this file" << std::endl;
        return;
    }
    std::string line;
    while (getline(fp, line)) {
        // using printf() in all tests for consistency
        if (line.front() == '#'|| line.front() == ';')
            continue;
        printf("%s\n", line.c_str());
        content.push_back(line);
    }
    fp.close();
    return;
}

class ImageClient {
public:
    ImageClient(std::shared_ptr<Channel> channel)
        : stub_(ImgDemo::NewStub(channel)) {//stub底层对应着一个tcp socket,销毁stub就会断开tcp连接
    }
    void resImgFetched(const std::string& imgname) {
        BaseName query;
        ClientContext context;
        query.add_name(imgname);
        std::unique_ptr<ClientReader<ImgTransmit::ImgInfo> > reader(stub_->resImgFetched(&context, query));
        ImgTransmit::ImgInfo info;
        //info 将会在服务端的resImgFetched中被处理
        while (reader->Read(&info)) {//将流读完
            std::cout << "Found feature " << info.name() << std::endl;
        }
        Status status = reader->Finish();
        if (status.ok()) {
            std::cout << " result fetch rpc succeeded."<< std::endl;
        }
        else {
            std::cout << "result fetch rpc failed." << std::endl;
        }
    }

    void resDescFetched(const std::string& imgname) {
        BaseName query;
        Description res;
        ClientContext context;
        query.add_name(imgname);
        Status status = stub_->resDescFetched(&context, query, &res);
        if (!status.ok()) {
            std::cout << "GetFeature rpc failed." << std::endl;
            return;
        }
        auto descs = res.desc();
        int size = descs.size();
        for (int i = 0; i < size; i++) {
            const std::string& description = descs.Get(i);
            std::cout << "get success: " << description << std::endl;
        }
        return;
    }

    void ImgUpload(const std::vector<std::string>& img_list) {
        My_Status stats;
        ClientContext context;
        const int kPoints = 10;
        unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();

        std::default_random_engine generator(seed);
        std::uniform_int_distribution<int> delay_distribution(500, 1500);
        
        //这个stats将会在服务端的ImgUpload被处理
        std::unique_ptr<ClientWriter<::ImgTransmit::ImgInfo> > writer(stub_->ImgUpload(&context, &stats));
        std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now();
        for (int i = 0; i < img_list.size(); i++) {
            std::string path = img_list.at(i);
            
            std::ifstream imgreader(path, std::ifstream::in | std::ios::binary);
            if(!imgreader.is_open())
                continue;
            imgreader.seekg(0, imgreader.end);  //将文件流指针定位到流的末尾
            int length = imgreader.tellg();
            
            imgreader.seekg(0, imgreader.beg);  //将文件流指针重新定位到流的开始
            char* buffer = (char*)malloc(sizeof(char) * length);
            imgreader.read(buffer, length);
            imgreader.close();
            
            int crest = path.find_last_of('\\');
            
            ImgInfo_Img img_detail;
            ImgInfo_ImgType type = ImgInfo_ImgType::ImgInfo_ImgType_JPG;
            img_detail.set_height(224);
            img_detail.set_width(224);
            img_detail.set_channel(3);
            img_detail.set_type(type);
            img_detail.set_data(buffer, length);
            free(buffer);      

            ImgTransmit::ImgInfo info;
            info.set_name(path.substr(crest + 1, -1));
            google::protobuf::Map<google::protobuf::int32, ImgInfo_Img>* maps =info.mutable_maps();
            google::protobuf::MapPair<google::protobuf::int32, ImgInfo_Img> item(i, img_detail);
            //maps->operator[](i)= img_detail;
            maps->insert(item);

            if (!writer->Write(info)) {
                // Broken stream.
                break;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(delay_distribution(generator)));
        }
        writer->WritesDone();
        std::chrono::system_clock::time_point end_time = std::chrono::system_clock::now();
        Status status = writer->Finish();
        
        if (status.ok()) {
            std::cout << "ImgUpload finished  with code: " << stats.code() <<  std::endl;
            Ms ms = std::chrono::duration_cast<Ms>(end_time - start_time);
            TimePoint<Ms> ms_time_point(ms);
            std::cout << "time cost is: " << ms_time_point.time_since_epoch().count() << " ms."<< std::endl;
        }
        else {
            std::cout << "ImgUpload rpc failed with code: "<<stats.code()<< std::endl;
        }
    }

private:
    std::unique_ptr<ImgDemo::Stub> stub_;
};

int main(int argc, char** argv) {
    ImageClient client(grpc::CreateChannel("localhost:50057",grpc::InsecureChannelCredentials()));
    std::cout << "-------------- upload image --------------" << std::endl;
    std::cout << "please assign the img list file:" << std::endl;
    std::vector<std::string> imglist;
    imglist.push_back("/path/to/image/275eef8456311c49af5e6137d959e1de.jpg");
    imglist.push_back("/path/to/image/98bb90c44f55aaeeae417d8233226785.jpg");
    client.ImgUpload(imglist);
    std::cout << "-------------- fetch image result --------------" << std::endl;
    client.resImgFetched("98bb90c44f55aaeeae417d8233226785.jpg");
    std::cout << "-------------- fetch description result --------------" << std::endl;
    client.resDescFetched("98bb90c44f55aaeeae417d8233226785.jpg");
    std::cin.get();
    return 0;
}

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
gRPC实现流式传输可以使用流式请求和流式响应。对于Java,您可以使用gRPC提供的Stub接口来实现流式传输。 例如,实现流式请求: ```java public class MyService extends MyServiceGrpc.MyServiceImplBase { @Override public void myMethod(StreamObserver<MyResponse> responseObserver) { // 处理请求 while (/* 请求未完成 */) { MyResponse response = getNextResponse(); responseObserver.onNext(response); } // 请求已完成 responseObserver.onCompleted(); } } ``` 在这个例子中,`myMethod`方法接收一个`StreamObserver`对象,它可以用来观察响应流。然后,您可以在`while`循环中处理请求,并逐个发送响应。最后,当请求完成时,调用`onCompleted`方法。 如果您需要实现流式响应,可以使用`StreamObserver`对象来处理请求流: ```java public class MyService extends MyServiceGrpc.MyServiceImplBase { @Override public void myMethod(MyRequest request, StreamObserver<MyResponse> responseObserver) { // 处理请求 while (/* 请求未完成 */) { MyResponse response = getNextResponse(); responseObserver.onNext(response); } // 请求已完成 responseObserver.onCompleted(); } } ``` 在这个例子中,`myMethod`方法接收一个`MyRequest`对象,它包含了请求信息。然后,您可以在`while`循环中处理响应,并逐个发送响应。最后,当响应完成时,调用`onCompleted`方法。 以上是基本的流式传输实现,您可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值