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有兩個用途:
- 禁止虛函數被重寫;
- 禁止基類被繼承。(基類就是父類,派生類就是子類。)
C++11中允許將類標記爲final,直接在類名稱後面使用關鍵字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 去等待操作結束。如果標籤出現,表示對應的操作已經完成。
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;
}
}
簡單通過獲取版本號測試了異步數據傳輸。
記錄下自己過程測試實現的接口,方便後續參考修改。
長路漫漫 惟劍做伴