已经折腾grpc几天了,也基本搞明白了怎么用,这里作一个简单的记录,以便往后须要的时候有个参考。ios
按照顺序,先写同步服务的简单实例,而后写异步服务的,最后写4中服务类型的使用。c++
grpc源码的example目录下都有相关的实例,可是讲的不够清楚,特别是异步服务这一块,注释说明不够详尽,CallData的封装也不利于理接异步服务的流程结构。因此我这里不按照官方的示例来作了。git
参考资料github
如何编写proto文件,能够看Protobuf3 语法指南 (英文原文地址https://developers.google.com/protocol-buffers/docs/proto3)中的说明,这里就再也不叙述了。安全
我这里写一个简单的simple.proto文件,定义三个简单的服务接口,流式接口之后再说。bash
syntax="proto3"; // 包名是生成代码的使用的namespace,全部代码都在这个下面 package Simple; // 指定服务的名称,做为生成代码里面的二级namespace service Server { // 测试接口一 rpc Test1(TestRequest) returns (TestNull ){} // 测试接口二 rpc Test2(TestNull ) returns (TestReply) {} // 测试接口三 rpc Test3(TestRequest) returns (TestReply) {} } message TestNull { } message TestRequest { string name = 1; // 客户端名称 int32 id = 2; // 客户端IP double value = 3; // 附带一个值 } message TestReply { int32 tid = 1; // 服务线程ID string svrname = 2; // 服务名称 double takeuptime = 3; // 请求处理耗时 }
上面的接口中,必须有参数和返回值,若是不须要参数或者返回值,也必须定义一个空的(没有成员)message,不然没法经过编译。服务器
安装好grpc以后,可使用grpc的相关命令行程序,来使用proto文件生成C++代码(也能够生成别的语言的),这里须要分两步,一是生成protobuf
(反)序列化的代码,二是生成基本服务框架代码。多线程
# 一、生成protobuf序列化代码 # 执行下面命令后,将在当前目录下生成 simple.pb.h 和 simple.pb.cc 文件 protoc -I ./ --cpp_out=. simple.proto # 二、生成服务框架代码 # 执行下面命令后,将在当前目录下生成 simple.grpc.pb.h 和 simple.grpc.pb.cc 文件 protoc -I ./ --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` simple.proto # 上面的 `which grpc_cpp_plugin` 也能够替换为 grpc_cpp_plugin 程序的路径(若是不在系统PATH下) # 生成的代码须要依赖第一步生成序列化代码,因此在使用的时候必须都要有(生成的时候不依赖)
若是本身生成比较麻烦,能够在这个网站上生成 protobuf compilerapp
查看上一步骤生成的代码(simple.grpc.pb.cc),能够看到它已经生成了服务代码Simple::Server::Service
类,里面已经有三个Test
函数的空实现,这三个函数都是虚函数,因此能够继承这个类,并实现这三个函数,也能够直接修改生成的代码,把这三个函数的实现改成本身的实现。
#include "simple.grpc.pb.h" #include <grpcpp/grpcpp.h> #include <memory> #include <iostream> #include <strstream> // 继承自生成的Service类,实现三个虚函数 class ServiceImpl: public Simple::Server::Service { public: // Test1 实现都是差不都的,这里只是为了测试,就随便返回点数据了 grpc::Status Test1(grpc::ServerContext* context, const Simple::TestRequest* request, Simple::TestNull* response) override { std::ostrstream os; os << "Client Name = " << request->name() << '\n'; os << "Clinet ID = " << request->id() << '\n'; os << "Clinet Value= " << request->value()<< '\n'; std::string message = os.str(); // grpc状态能够设置message,因此也能够用来返回一些信息 return grpc::Status(grpc::StatusCode::OK,message); } // Test2 grpc::Status Test2(grpc::ServerContext* context, const Simple::TestNull* request, Simple::TestReply* response) override { response->set_tid(100); response->set_svrname("Simple Server"); response->set_takeuptime(0.01); return grpc::Status::OK; } // Test3 grpc::Status Test3(grpc::ServerContext* context, const Simple::TestRequest* request, Simple::TestReply* response) override { std::ostrstream os; os << "Client Name = " << request->name() << '\n'; os << "Clinet ID = " << request->id() << '\n'; os << "Clinet Value= " << request->value()<< '\n'; std::string message = os.str(); response->set_tid(__LINE__); response->set_svrname(__FILE__); response->set_takeuptime(1.234); // grpc状态能够设置message return grpc::Status(grpc::StatusCode::OK,std::move(message)); } }; int main() { // 服务构建器,用于构建同步或者异步服务 grpc::ServerBuilder builder; // 添加监听的地址和端口,后一个参数用于设置认证方式,这里选择不认证 builder.AddListeningPort("0.0.0.0:33333",grpc::InsecureServerCredentials()); // 建立服务对象 ServiceImpl service; // 注册服务 builder.RegisterService(&service); // 构建服务器 std::unique_ptr<grpc::Server> server(builder.BuildAndStart()); std::cout<<"Server Runing"<<std::endl; // 进入服务处理循环(必须在某处调用server->Shutdown()才会返回) server->Wait(); return 0; }
写完代码以后,须要进行编译。编译的命令以下:
g++ -o server server.cpp simple.grpc.pb.cc simple.pb.cc \ -std=c++11 -I. `pkg-config --cflags protobuf grpc` \ `pkg-config --libs protobuf grpc++ grpc` \ -pthread -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed -ldl # 由于我这里没有grpc.pc文件,致使pkg-config找不到相关配置,因此我实际使用下面命令编译 g++ -o service service.cpp simple.grpc.pb.cc simple.pb.cc -std=c++11 -I. -lgrpc++ -lgrpc -lprotobuf -lgpr -lz -lcares -laddress_sorting -lpthread -Wno-deprecated
这里本身编译的grpc或者安装的可能没有grpc.pc
文件,致使pkg-config程序找不到相关的配置,若是没有就直接连接如下的库便可:
# VC下使用 grpc.lib grpc++.lib libprotobuf.lib gpr.lib zlib.lib cares.lib address_sorting.lib ws2_32.lib # gcc/clang下使用 -lgrpc++ -lgrpc -lprotobuf -lgpr -lz -lcares -laddress_sorting -lpthread
客户端代码比服务端简单多了,下面直接给出代码(由于比较简单,这里就只写Test3
的调用代码了)。
关于客户端,能够看一下这篇文章从grpc源码讲起(Client端的消息发送)
客户端这里只写了一个接口的测试,另外两个也都是差很少的,就不写了。
#include "simple.grpc.pb.h" #include <grpcpp/grpcpp.h> #include <memory> #include <iostream> int main() { // 建立一个链接服务器的通道 std::shared_ptr<grpc::Channel> channel = grpc::CreateChannel("localhost:33333", grpc::InsecureChannelCredentials()); // 建立一个stub std::unique_ptr<Simple::Server::Stub> stub = Simple::Server::NewStub(channel); // 上面部分能够复用,下面部分复用的话要本身考虑多线程安全问题 { // 建立一个请求对象,用于打包要发送的请求数据 Simple::TestRequest request; // 建立一个响应对象,用于解包响要接收的应数据 Simple::TestReply reply; // 建立一个客户端上下文。它能够用来向服务器传递附加的信息,以及能够调整某些RPC行为 grpc::ClientContext context; // 发送请求,接收响应 grpc::Status st = stub->Test3(&context,request,&reply); if(st.ok()){ // 输出下返回数据 std::cout<< "tid = " << reply.tid() << "\nsvrname = " << reply.svrname() << "\ntakeuptime = " << reply.takeuptime() << std::endl; } else { // 返回状态非OK std::cout<< "StatusCode = "<< st.error_code() <<"\nMessage: "<< st.error_message() <<std::endl; } } }
客户端的编译和服务是同样的,只是把server.cpp
改成client.cpp
便可。
g++ -o client client.cpp simple.grpc.pb.cc simple.pb.cc -std=c++11 -I. -lgrpc++ -lgrpc -lprotobuf -lgpr -lz -lcares -laddress_sorting -lpthread -Wno-deprecated
编译了server和client后,都运行起来测试了一下,可行。
服务端这里输出,是由于我在代码里面加了一行输出,在Test3函数中输出了一下函数名(__func__
)和行号(__LINE__
)。
这里推荐一个工具BloomRPC ,这是一个GRPC服务的可视化界面客户端程序,能够直接加载proto文件,发送请求并接收返回。(我这里没法发送到非本地服务器,不知道是否是这个软件的缘由)