已經摺騰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文件,發送請求並接收返回。(我這裏沒法發送到非本地服務器,不知道是否是這個軟件的緣由)