gRPC-- sync async服務實現(C++版)


1. 同步rpc服務器實現

同步 RPC 調用一直會阻塞直到從服務端獲得一個應答。

1.1 創建同步服務類

class GRPC_SyncServer final : public ModemManager::Service
{
    public:
        Status GetInfo(ServerContext* context, const ModemManagerFilterMessage* request, ModemInfoMessage* reply);
        Status GetModemDataStream(ServerContext* context, const ModemManagerFilterMessage* request, ServerWriter<ModemInfoMessage>* responder);
        void RunServer(void);
};

裏面包含要實現的rpc功能接口。

C++11的關鍵字final有兩個用途:

  1. 禁止虛函數被重寫;
  2. 禁止基類被繼承。(基類就是父類,派生類就是子類。)

C++11中允許將類標記爲final,直接在類名稱後面使用關鍵字final,繼承該類會導致編譯錯誤。

c++類禁止繼承final

GRPC_SyncServer繼承ModemManager::Service,咋一看覺得奇怪怎麼還能繼承呢?跟進源碼上一看,原來繼承ModemManager成員Service這個類並沒有final字段修飾。

class ModemManager final {
 public:
  static constexpr char const* service_full_name() {
    return "cater.ModemManager";
  }
  .........
class Service : public ::grpc::Service {
 public:
  Service();
  virtual ~Service();
  // sync reads
  virtual ::grpc::Status GetInfo(::grpc::ServerContext* context, const ::cater::ModemManagerFilterMessage* request, ::cater::ModemInfoMessage* response);
  // stream async updates
  virtual ::grpc::Status GetModemDataStream(::grpc::ServerContext* context, const ::cater::ModemManagerFilterMessage* request, ::grpc::ServerWriter< ::cater::ModemInfoMessage>* writer);
};
  • 添加constexpr關鍵字的表達式是在編譯期執行,其他表達式在運行期間執行。
    constexpr關鍵字的作用

  • virtual析構函數的作用
    當定義了一個基類,並用來繼承的。當其他類用到基類,其析構函數必須是虛函數,不然在調用基類析構函數的時候,不會去調用子類的析構函數,從而會造成內存泄漏。

  • virtual繼承作用
    當基類的函數爲虛函數的時候,可以通過基類去訪問子類的函數。
    C++中Virtual的作用

  • 全局作用域符號:當全局變量在局部函數中與其中某個變量重名,可用::來區分。
    ::grpc::Status
    C++中雙冒號::的作用淺析

  • ModemManagerFilterMessage和ModemInfoMessage,是根據proto文件定義的message生成的。

  • 針對stream流式rpc需要通過ServerWriter來進行數據更新。

1.2 創建同步服務對象

/************************************************************************
**函數:GRPC_SyncServerStart
**功能:運行SyncServer服務
**參數:無
**返回:無
************************************************************************/
void GRPC_SyncServerStart()
{
  GRPC_SyncServer server;
  server.RunServer();
}
/************************************************************************
**函數:get_file_contents
**功能:讀取文件中的內容
**參數:[in] fpath:文件路徑
**返回:返回文件內容
************************************************************************/
std::string get_file_contents(const char *fpath)
{
  std::ifstream finstream(fpath);
  std::string contents;
  contents.assign((std::istreambuf_iterator<char>(finstream)),
                       std::istreambuf_iterator<char>());
  finstream.close();
  return contents;
}


/************************************************************************
**函數:RunServer
**功能:創建並運行grpc服務器
**參數:無
**返回:無
************************************************************************/
void GRPC_SyncServer::RunServer(void) {
  std::string server_address("localhost:50051");

  GRPC_SyncServer service;

  /* 構建並運行支持openssl雙向驗證服務器 */
  auto clientcert = get_file_contents(clientcert_path);
  auto servercert = get_file_contents(servercert_path);
  auto serverkey = get_file_contents(serverkey_path);

  grpc::SslServerCredentialsOptions::PemKeyCertPair pkcp;
  pkcp.private_key = serverkey;
  pkcp.cert_chain = servercert;

  grpc::SslServerCredentialsOptions ssl_opts;
  ssl_opts.pem_root_certs = clientcert;
  ssl_opts.pem_key_cert_pairs.push_back(pkcp);

  std::shared_ptr<grpc::ServerCredentials> creds;
  creds = grpc::SslServerCredentials(ssl_opts);

  ServerBuilder builder;
  builder.AddListeningPort(server_address, creds);
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "SyncServer listening on " << server_address << std::endl;

  server->Wait();
}

構建完ssl證書、綁定好域名端口,即可註冊同步服務。

2. 異步rpc服務器實現

異步rpc服務器,能夠在不阻塞當前線程的情況下啓動RPC。

文檔基本概述
gRPC 的異步操作使用CompletionQueue。 基本工作流如下:

  • 在 RPC 調用上綁定一個 CompletionQueue
  • 做一些事情如讀取或者寫入,以唯一的 void* 標籤展示
  • 調用 CompletionQueue::Next 去等待操作結束。如果標籤出現,表示對應的操作已經完成。

gRPC 官方文檔中文版_V1.0 異步基礎: C++

2.1 創建異步服務類

class GRPC_AsyncServer final {
 public:
  ~GRPC_AsyncServer() {
    server_->Shutdown();
    // Always shutdown the completion queue after the server.
    cq_->Shutdown();
  }
  void RunServer(void);
 private:
  // Class encompasing the state and logic needed to serve a request.
  class CallData {
   public:
    // Take in the "service" instance (in this case representing an asynchronous
    // server) and the completion queue "cq" used for asynchronous communication
    // with the gRPC runtime.
    CallData(ModemManager::AsyncService* service, ServerCompletionQueue* cq)
        : service_(service), cq_(cq), responder_(&ctx_), status_(CREATE) {
      // Invoke the serving logic right away.
      Proceed();
    }
    void Proceed();

   private:
    // The means of communication with the gRPC runtime for an asynchronous
    // server.
    ModemManager::AsyncService* service_;
    // The producer-consumer queue where for asynchronous server notifications.
    ServerCompletionQueue* cq_;
    // Context for the rpc, allowing to tweak aspects of it such as the use
    // of compression, authentication, as well as to send metadata back to the
    // client.
    ServerContext ctx_;

    // What we get from the client.
    ModemManagerFilterMessage request_;
    // What we send back to the client.
    ModemInfoMessage reply_;

    // The means to get back to the client.
    ServerAsyncWriter<ModemInfoMessage> responder_;

    // Let's implement a tiny state machine with the following states.
    enum CallStatus { CREATE, PROCESS, FINISH };
    CallStatus status_;  // The current serving state.
  };

  // This can be run in multiple threads if needed.
  void HandleRpcs();
  std::unique_ptr<ServerCompletionQueue> cq_;
  ModemManager::AsyncService service_;
  std::unique_ptr<Server> server_;
};

創建個完成隊列ServerCompletionQueue,並與rpc進行綁定。
通過CallData對象去維護每個RPC 的狀態,並且使用這個對象的地址作爲調用的唯一標籤。

2.2 創建異步服務對象

/************************************************************************
**函數:RunServer
**功能:創建並運行grpc服務器
**參數:無
**返回:無
************************************************************************/
void GRPC_AsyncServer::RunServer(void)
{
  std::string server_address("localhost:50051");
  auto clientcert = get_file_contents(clientcert_path); // for verifying clients
  auto servercert = get_file_contents(servercert_path);
  auto serverkey = get_file_contents(serverkey_path);

  grpc::SslServerCredentialsOptions::PemKeyCertPair pkcp;
  pkcp.private_key = serverkey;
  pkcp.cert_chain = servercert;

  grpc::SslServerCredentialsOptions ssl_opts;
  ssl_opts.pem_root_certs = clientcert;
  ssl_opts.pem_key_cert_pairs.push_back(pkcp);

  std::shared_ptr<grpc::ServerCredentials> creds;
  creds = grpc::SslServerCredentials(ssl_opts);

  //GRPC_SyncServer sync_service;
  ServerBuilder builder;
  // Listen on the given address with openssl authentication mechanism.
  builder.AddListeningPort(server_address, creds);
  // Register "service_" as the instance through which we'll communicate with
  // clients. In this case it corresponds to an *asynchronous* service.
  builder.RegisterService(&service_);
  // Get hold of the completion queue used for the asynchronous communication
  // with the gRPC runtime.
  cq_ = builder.AddCompletionQueue();
  // Finally assemble the server.
  server_ = builder.BuildAndStart();
  std::cout << "AsyncServer listening on " << server_address << std::endl;

  // Proceed to the server's main loop.
  HandleRpcs();
}
/************************************************************************
**函數:HandleRpcs
**功能:運行一個主循環去查詢隊列
**參數:無
**返回:無
************************************************************************/
void GRPC_AsyncServer::HandleRpcs() {
  // Spawn a new CallData instance to serve new clients.
  new CallData(&service_, cq_.get());
  void* tag;  // uniquely identifies a request.
  bool ok;
  while (true) {
    // Block waiting to read the next event from the completion queue. The
    // event is uniquely identified by its tag, which in this case is the
    // memory address of a CallData instance.
    // The return value of Next should always be checked. This return value
    // tells us whether there is any kind of event or cq_ is shutting down.
    GPR_ASSERT(cq_->Next(&tag, &ok));
    GPR_ASSERT(ok);
    static_cast<CallData*>(tag)->Proceed();
  }
}
/************************************************************************
**函數:Proceed
**功能:rpc服務實現
**參數:無
**返回:無
************************************************************************/
void GRPC_AsyncServer::CallData::Proceed()
{
  if (status_ == CREATE) {
    // Make this instance progress to the PROCESS state.
    status_ = PROCESS;

    // As part of the initial CREATE state, we *request* that the system
    // start processing SayHello requests. In this request, "this" acts are
    // the tag uniquely identifying the request (so that different CallData
    // instances can serve different requests concurrently), in this case
    // the memory address of this CallData instance.
    service_->RequestGetModemDataStream(&ctx_, &request_, &responder_, cq_, cq_, 
                                        this);
  } else if (status_ == PROCESS) {
    // Spawn a new CallData instance to serve new clients while we process
    // the one for this CallData. The instance will deallocate itself as
    // part of its FINISH state.
    new CallData(service_, cq_);

    // The actual processing.
    do {
      if (request_.modemmanagerfilter() == cater::HARDWARE_INFO) {
        char *hard_version_buf = NULL;

        hard_version_buf = get_hardware_version();
        if (NULL == hard_version_buf) {
          FK_TRACE_ERROR("get hardware version fail\n");
          break;
        }
        reply_.mutable_hardwareinfovalue()->set_hardwareversion(hard_version_buf);
        std::cout << reply_.mutable_hardwareinfovalue()->hardwareversion() << std::endl;
      }

      if (request_.modemmanagerfilter() == cater::SOFTWARE_INFO) {
        char *soft_version_buf = NULL;

        soft_version_buf = get_software_version();
        if (NULL == soft_version_buf) {
          FK_TRACE_ERROR("get software version fail\n");
          break;
        }
        reply_.mutable_softwareinfovalue()->set_softwareversion(soft_version_buf);
        std::cout << reply_.mutable_softwareinfovalue()->softwareversion() << std::endl;
      }
    } while(0);

    responder_.Write(reply_, this);
    // And we are done! Let the gRPC runtime know we've finished, using the
    // memory address of this instance as the uniquely identifying tag for
    // the event.
    status_ = FINISH;
    responder_.Finish(Status::OK, this);
  } else {
    GPR_ASSERT(status_ == FINISH);
    // Once in the FINISH state, deallocate ourselves (CallData).
    delete this;
  }
}

簡單通過獲取版本號測試了異步數據傳輸。

記錄下自己過程測試實現的接口,方便後續參考修改。

長路漫漫 惟劍做伴

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章