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

已經摺騰grpc幾天了,也基本搞明白了怎麼用,這裏作一個簡單的記錄,以便往後須要的時候有個參考。ios

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

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

參考資料github

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

如何編寫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,不然沒法經過編譯。服務器

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

安裝好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函數的空實現,這三個函數都是虛函數,因此能夠繼承這個類,並實現這三個函數,也能夠直接修改生成的代碼,把這三個函數的實現改成本身的實現。

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

四、編寫客戶端代碼

客戶端代碼比服務端簡單多了,下面直接給出代碼(由於比較簡單,這裏就只寫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

五、簡單測試一下

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

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

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

相關文章
相關標籤/搜索