下面实验都是定义的同一个函数进行远程过程调用,
因此c++,python的客户端和服务端都是互通的
grpc实例之Python实现
grpc实例之C++实现
grpc详解和安装

一. 创建配置文件(极其简单)

这个配置文件msg.protogrpc实例之Python实现用的是一个文件
这里不进行赘述了

// msg.proto
syntax = "proto3"; // 规定使用proto3的语法
 
service MsgService { // 定义服务, 流数据放到括号里面
	rpc GetMsg (MsgRequest) returns (MsgResponse){}
}
 
message MsgRequest { // 请求的结构, 也可以定义int32,int64,double,float
	string name = 1;
	int32 num1 = 2;
	double num2 = 3;
}
 
message MsgResponse { // 回应的结果
	string msg = 1;
	int32 num1 = 2;
	double num2 = 3;
}

二. 生成C++头文件

2.1 如何确定命令

  • 因为grpc给出了一个helloworld, 所以可以根据里面的Makefile文件知道他的编译逻辑
  • 首先是将第一章中的配置文件通过proto命令生成对应的应用头文件(msg.grpc.pb)和消息头文件(msg.pb), 使用的命令如下
# 生成`msg.grpc.pb` 服务类
protoc -I ../ --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` ../msg.proto
# ../是.proto文件所在文件夹, ../msg.proto是文件 --grpc_out=.这个点表示文件保存到当前文件夹

# 生成`msg.pb` 消息类
protoc -I ../ --cpp_out=. ../msg.proto
# ../是.proto文件所在文件夹, ../msg.proto是文件 --grpc_out=.这个点表示文件保存到当前文件夹

三. 服务器端

  • 引入刚刚生成的头文件和grpc头文件
  • 定义类继承MsgService::Service
  • 实现GetMsg方法
  • 所有的参数赋值都通过方法的set_argc*()实现
  • 运行服务即可
#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h> // 和python一样, import grp

// 在包含两个信息和应用的头文件
#include "msg.grpc.pb.h"
#include "msg.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;

// service MsgService { // 定义服务, 流数据放到括号里面
//   rpc GetMsg (MsgRequest) returns (MsgResponse){}
// }
 
// message MsgRequest { // 请求的结构, 也可以定义int32,int64,double,float
//   string name = 1;
//   int32 num1 = 2;
//   double num2 = 3;
// }
 
// message MsgResponse { // 回应的结果
//   string msg = 1;
//   int32 num1 = 2;
//   double num2 = 3;
// }
// 
// 不知道为啥, 这里生成并没有定义namespace, 所以可以在引用头文件之后直接使用
// using msg::MsgRequest; //上面定义的请求结构
// using msg::MsgResponse; // 上面定义的响应结构
// using msg::MsgService; // 上面定义的类
// 

// 这个类随意起名, 只要继承 MsgService::Service就行了
// 具体见msg.grpc.pb.cc中MsgService::Service::Service() 
class MyMsgService final : public MsgService::Service {
  Status GetMsg(ServerContext* context, const MsgRequest* request,
                  MsgResponse* reply) override {

    std::string str1("Hello ");
    reply->set_msg(str1 + request->name()); // 给reply.msg赋值
    reply->set_num1(32);
    reply->set_num2(3.14155);
    
    return Status::OK;
  }
};

void RunServer() {
  std::string server_address("0.0.0.0:50051");
  MyMsgService service;

  ServerBuilder builder;
  // 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) {
  RunServer();

  return 0;
}

四. 客户端

  • 引入刚刚生成的头文件和grpc头文件
  • 定义类继承MsgServiceClient
  • 实现GetMsg方法
  • 所有的参数赋值都通过方法的set_argc*()实现
  • 运行服务即可
#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h> // 和python一样, import grp

// 在包含两个信息和应用的头文件
#include "msg.grpc.pb.h"
#include "msg.pb.h"

// 这是通用工具
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;



// service MsgService { // 定义服务, 流数据放到括号里面
//   rpc GetMsg (MsgRequest) returns (MsgResponse){}
// }
 
// message MsgRequest { // 请求的结构, 也可以定义int32,int64,double,float
//   string name = 1;
//   int32 num1 = 2;
//   double num2 = 3;
// }
 
// message MsgResponse { // 回应的结果
//   string msg = 1;
//   int32 num1 = 2;
//   double num2 = 3;
// }
// 
// 不知道为啥, 这里生成并没有定义namespace, 所以可以在引用头文件之后直接使用
// using msg::MsgRequest; //上面定义的请求结构
// using msg::MsgResponse; // 上面定义的响应结构
// using msg::MsgService; // 上面定义的类

class MsgServiceClient {
 public:
  MsgServiceClient(std::shared_ptr<Channel> channel)
      : stub_(MsgService::NewStub(channel)) {}


  MsgResponse GetMsg(const std::string& user, int num1, double num2) {
    // 请求数据数据格式化到request
    MsgRequest request; 
    request.set_name(user);
    request.set_num1(num1);
    request.set_num2(num2);

    // 服务器返回端
    MsgResponse reply;

    //客户端上下文。它可以用来传递额外的信息
    //服务器和/或调整某些RPC行为。
    ClientContext context;

    // The actual RPC.
    Status status = stub_->GetMsg(&context, request, &reply);

    // Act upon its status.
    if (status.ok()) {
      // std::cout<<reply.msg()<<std::endl;
      // printf("num1 = %d;  num2=%f\n", reply.num1(), reply.num2());
      // return reply.msg();  // reply.msg(), reply.num1(), reply.num2();
      return reply;  // reply.msg(), reply.num1(), reply.num2();
    } else {
      std::cout << status.error_code() << ": " << status.error_message()
                << std::endl;
      return reply;
    }
  }

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

int main(int argc, char** argv) {
  MsgServiceClient z_msg(grpc::CreateChannel(
      "localhost:50051", grpc::InsecureChannelCredentials()));
  std::string user("world");
  // std::string reply = z_msg.GetMsg(user, 234, 3.1415926);
  MsgResponse reply = z_msg.GetMsg(user, 234, 3.1415926);
  std::cout<<reply.msg()<<std::endl;
  printf("num1 = %d;  num2=%f\n", reply.num1(), reply.num2());
  // std::cout << "Greeter received: " << reply << std::endl;
  return 0;
}

五. 编译文件Makefile

  • 设置proto路径
  • 设置system-check生成对应头文件
  • 设置编译client和server
  • 完成
# add by zjq 
# to test cpp

HOST_SYSTEM = $(shell uname | cut -f 1 -d_)
SYSTEM ?= $(HOST_SYSTEM)
CXX = g++
CPPFLAGS += `pkg-config --cflags protobuf grpc`
CXXFLAGS += -std=c++11
# 如果是win, else Ubuntu
ifeq ($(SYSTEM),Darwin) 
LDFLAGS += -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc`\
           -lgrpc++_reflection\
           -ldl
else
LDFLAGS += -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc`\
           -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed\
           -ldl
endif

# protoc根据.proto文件生成对应的头文件
PROTOC = protoc # 工具名称, 如命令行要运行的protoc **
GRPC_CPP_PLUGIN = grpc_cpp_plugin
GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)`
PROTOS_PATH = ../ # 存放.proto文件的相对位置
vpath %.proto $(PROTOS_PATH)



# 显然, 先检测是否已经生成过对应的头文件了
all: system-check client server
client: msg.pb.o msg.grpc.pb.o msg_client.o
	$(CXX) $^ $(LDFLAGS) -o $@
	echo "生成client命令执行的是: $(CXX) $^ $(LDFLAGS) -o $@"
server: msg.pb.o msg.grpc.pb.o msg_server.o
	$(CXX) $^ $(LDFLAGS) -o $@
	echo "生成server命令执行的是: $(CXX) $^ $(LDFLAGS) -o $@"
clean:
	rm -f *.o client server *.pb.cc *.pb.h


# 其实这句最关键, 用于利用proto工具, 将.proto文件生成对应的信息管道头文件
.PRECIOUS: %.grpc.pb.cc
%.grpc.pb.cc: %.proto
	$(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $<
	echo "$(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $<"
.PRECIOUS: %.pb.cc
%.pb.cc: %.proto
	$(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $<



# The following is to test your system and ensure a smoother experience.
# They are by no means necessary to actually compile a grpc-enabled software.
# 下面是确保系统内已经安装了grpc和protoc

PROTOC_CMD = which $(PROTOC)
PROTOC_CHECK_CMD = $(PROTOC) --version | grep -q libprotoc.3
PLUGIN_CHECK_CMD = which $(GRPC_CPP_PLUGIN)
HAS_PROTOC = $(shell $(PROTOC_CMD) > /dev/null && echo true || echo false)
ifeq ($(HAS_PROTOC),true)
HAS_VALID_PROTOC = $(shell $(PROTOC_CHECK_CMD) 2> /dev/null && echo true || echo false)
endif
HAS_PLUGIN = $(shell $(PLUGIN_CHECK_CMD) > /dev/null && echo true || echo false)

SYSTEM_OK = false
ifeq ($(HAS_VALID_PROTOC),true)
ifeq ($(HAS_PLUGIN),true)
SYSTEM_OK = true
endif
endif

system-check:
ifneq ($(HAS_VALID_PROTOC),true)
	@echo " DEPENDENCY ERROR"
	@echo
	@echo "You don't have protoc 3.0.0 installed in your path."
	@echo "Please install Google protocol buffers 3.0.0 and its compiler."
	@echo "You can find it here:"
	@echo
	@echo "   https://github.com/google/protobuf/releases/tag/v3.0.0"
	@echo
	@echo "Here is what I get when trying to evaluate your version of protoc:"
	@echo
	-$(PROTOC) --version
	@echo
	@echo
endif
ifneq ($(HAS_PLUGIN),true)
	@echo " DEPENDENCY ERROR"
	@echo
	@echo "You don't have the grpc c++ protobuf plugin installed in your path."
	@echo "Please install grpc. You can find it here:"
	@echo
	@echo "   https://github.com/grpc/grpc"
	@echo
	@echo "Here is what I get when trying to detect if you have the plugin:"
	@echo
	-which $(GRPC_CPP_PLUGIN)
	@echo
	@echo
endif
ifneq ($(SYSTEM_OK),true)
	@false
endif

六. 文件结构

```shell
├── c_test
│   ├── Makefile
│   ├── client
│   ├── msg.grpc.pb.cc
│   ├── msg.grpc.pb.h
│   ├── msg.grpc.pb.o
│   ├── msg.pb.cc
│   ├── msg.pb.h
│   ├── msg.pb.o
│   ├── msg_client.cc
│   ├── msg_client.o
│   ├── msg_server.cc
│   ├── msg_server.o
│   └── server
├── msg.proto
└── python_test
    ├── msg.proto # 这个文件和外面的文件一样, 可以删除
    ├── msg_client.py
    ├── msg_pb2.py
    ├── msg_pb2_grpc.py
    ├── msg_server.py
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐