主要参考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;
}