grpc使用記錄(二)簡單同步服務實例


已經折騰grpc幾天了,也基本搞明白了怎么用,這里做一個簡單的記錄,以便日后需要的時候有個參考。

按照順序,先寫同步服務的簡單實例,然后寫異步服務的,最后寫4中服務類型的使用。

grpc源碼的example目錄下都有相關的實例,但是講的不夠清楚,特別是異步服務這一塊,注釋說明不夠詳盡,CallData的封裝也不利於理接異步服務的流程結構。所以我這里不按照官方的示例來做了。

參考資料

1、編寫proto文件,定義服務

如何編寫proto文件,可以看Protobuf3 語法指南 (英文原文地址https://developers.google.com/protocol-buffers/docs/proto3)中的說明,這里就不再敘述了。

我這里寫一個簡單的simple.proto文件,定義三個簡單的服務接口,流式接口以后再說。

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,否則無法通過編譯。

2、編譯proto文件,生成代碼

安裝好grpc之后,可以使用grpc的相關命令行程序,來使用proto文件生成C++代碼(也可以生成別的語言的),這里需要分兩步,一是生成protobuf(反)序列化的代碼,二是生成基本服務框架代碼。

# 1、生成protobuf序列化代碼
# 執行下面命令后,將在當前目錄下生成 simple.pb.h 和 simple.pb.cc 文件
protoc -I ./ --cpp_out=. simple.proto

# 2、生成服務框架代碼
# 執行下面命令后,將在當前目錄下生成 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 compiler

3、編寫服務端代碼

查看上一步驟生成的代碼(simple.grpc.pb.cc),可以看到它已經生成了服務代碼Simple::Server::Service類,里面已經有三個Test函數的空實現,這三個函數都是虛函數,所以可以繼承這個類,並實現這三個函數,也可以直接修改生成的代碼,把這三個函數的實現改為自己的實現。

server.cpp 代碼


#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

4、編寫客戶端代碼

客戶端代碼比服務端簡單多了,下面直接給出代碼(因為比較簡單,這里就只寫Test3的調用代碼了)。

關於客戶端,可以看一下這篇文章從grpc源碼講起(Client端的消息發送)

client.cpp代碼

客戶端這里只寫了一個接口的測試,另外兩個也都是差不多的,就不寫了。

#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

5、簡單測試一下

編譯了serverclient后,都運行起來測試了一下,可行。

服務端這里輸出,是因為我在代碼里面加了一行輸出,在Test3函數中輸出了一下函數名(__func__)和行號(__LINE__)。

這里推薦一個工具BloomRPC ,這是一個GRPC服務的可視化界面客戶端程序,可以直接加載proto文件,發送請求並接收返回。(我這里無法發送到非本地服務器,不知道是不是這個軟件的原因)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM