基於Protobuf的通訊庫--Poppy簡介

第一步:定義協議

    定義協議只需要編寫一個proto文件即可。


    範例:echo_service.proto


// 定義你自己的 package,package會被映射到C++中的namespace,爲了避免可能的衝突,強烈建議總是使用package。
package rpc_examples;

// 這是請求消息
message EchoRequest {
    required string user = 1;
    required string message = 2;
}
// 這是迴應消息
message EchoResponse {
    required string user = 1;
    required string message = 2;
}
// 這是服務,只包含一個方法,Service 的命名建議以 Service 爲後綴。
service EchoService {
    rpc SimpleEcho(EchoRequest) returns(EchoResponse);
}


    編譯proto文件的功能已經集成到了Blade裏,自動生成接口定義文件echo_service.pb.h和echo_service.pb.cc,不需要自己動手。注意這裏表面上EchoRequest和EchoResponse的成員完全相同。是因爲這只是例子而已,實際中請求與迴應往往有很大差異。



第二步:實現服務器

    1、必須要包含的頭文件


#include "poppy/rpc_server.h"               // 這是poppy的
#include "poppy/examples/echo_service.pb.h"   // 這是自己定義service的








 其中 echo_service.pb.h是 protoc 編譯器生成的。


    2、繼承編譯生成的服務接口類,實現其各個方法:


class EchoServiceImpl : public EchoService {
private:
    // 實現服務器端的 Handler 方法
    // 方法名就是 proto 中的方法名
    // 第一個參數固定爲 controller,後面兩個參數分別是請求和迴應的消息
    // 你可以讀取請求消息,填充迴應消息,一切都就緒後,調done->Run()就完成了對請求的處理。
    virtual void SimpleEcho(
        google::protobuf::RpcController* controller,
        const EchoRequest* request,
        EchoResponse* response,
        google::protobuf::Closure* done)
    {
        // 填充迴應消息,實際代碼往往還需要讀取請求消息。
        response->set_user(request->user());
        response->set_message(
             "simple echo from server: " + FLAGS_server_address +
             ", message: " + request->message());
        LOG(INFO) << "request: " << request->message();
        LOG(INFO) << "response: " << response->message();
        done->Run(); // 處理完成後調用 done->Run() 結束
    }
};




      注意調了done->Run()之後,所有的四個參數都不再能訪問。只要 done 還沒 Run,就還有效。 這裏演示的是簡單的同步處理,因此如果把done轉到別的線程裏運行,就實現了異步處理。


    3、把服務對象註冊給RPC Server,並啓動服務


int main()
{
    // 定義 rpc_server 對象。
    poppy::RpcServer rpc_server;
    // 創建 service
    rpc_examples::EchoServerImpl* echo_service = new rpc_examples::EchoServerImpl();

    // 註冊給 rpc_server,註冊後,echo_service 就由 rpc_server 來負責釋放了。
    rpc_server.RegisterService(echo_service);
    // 啓動服務器
    if (!rpc_server.Start("10.6.222.21:10000")) {
        return EXIT_FAILURE;
    }

    // 運行 rpc_server,可以被信號退出。
    return rpc_server.Run();
}

 第三步:實現客戶端

    1、包含頭文件

#include "poppy/examples/echo_service.pb.h"
#include "poppy/rpc_client.h"



    其中 echo_service.pb.h是 protoc 編譯器生成的。


    2、同步調用


    同步調用就是像大多數本地函數一樣,調用者發起後,等待被調過程返回,然後繼續。


// 定義 client 對象,一個 client 程序只需要一個 client 對象。
poppy::RpcClient rpc_client;
// 定義 channel,代表通訊通道,每個服務器地址對應一個 channel。
poppy::RpcChannel rpc_channel(&rpc_client, "10.6.222.21:10000");

// 定義代表 Service 在 Client 端的表示:Stub 對象。
rpc_examples::EchoServer::Stub echo_client(&rpc_channel); 

// 定義和填充調用的請求消息。
rpc_examples::EchoRequest request;

request.set_user("echo_test_user");
request.set_message("hello, poppy server!");
// 定義方法的迴應消息,會在調用返回後被填充。
rpc_examples::EchoResponse response;

// 定義  controller,代表本次調用。
poppy::RpcController rpc_controller;
// 發起調用,最後一個參數爲 NULL 即爲同步調用。
echo_client.SimpleEcho(&rpc_controller, &request, &response, NULL);


3、異步調用


    異步調用則是,調用者發起後,不等待被調過程返回,就繼續。因爲可以同時發起多個請求,因此異步模式性能高一些。不過用起來也略微麻煩一些。異步調用完成後,通過回調函數來通知調用者:


// 定義異步調用完成時的回調函數
void EchoCallback(poppy::RpcController* controller,
    rpc_examples::EchoRequest* request,
    rpc_examples::EchoResponse* response)
{
    LOG(INFO)
        << "request: " << controller->sequence_id()
        << ", message: " << request->message();

if (controller->Failed()) {
        LOG(INFO) << "failed: " << controller->ErrorText();
    } else {
        LOG(INFO) << "response: " << response->message();
    }

    // 清理異步調用分配的資源
    delete controller;
    delete request;
    delete response;
}

poppy::RpcClient rpc_client;
poppy::RpcChannel rpc_channel(&rpc_client, "10.6.222.21:10000");
rpc_examples::EchoServer::Stub echo_client(&rpc_channel);
// 異步調用時,回調運行前,request,reponse,controller 都必須一直有效。
// 因此這裏用 new 創建。調用完成後,用戶可以回收或者釋放,done則由Poppy來釋放。rpc_examples::EchoRequest* request = new rpc_examples::EchoRequest();
request->set_user("echo_test_user");
request->set_message("hello, poppy server!");

rpc_examples::EchoResponse* response = new rpc_examples::EchoResponse();
poppy::RpcController* rpc_controller = new poppy::RpcController();
google::protobuf::Closure* done = NewClosure(&EchoCallback,
        rpc_controller, request, response);

// 無需等待,EchoCallback 就會在將來完成或者失敗時被調用。
echo_client.SimpleEcho(rpc_controller, request, response, done);


    回調函數也可以是成員函數,具體參考 Closure test 裏的用法。

程接口 

    Poppy的API都在poppy命名空間下,下面描述均省略命名空間。


RpcServer 

    該類僅用於服務器端。它是服務器端的具體業務服務對象的容器,負責監聽和接收客戶端的請求,分發並調用實際的服務對象方法並將結果回送給客戶端。 server程序的實現者需要把具體Service註冊給RpcServer。


    RpcServer 從 HttpServer 派生,因此也可以註冊 Http Handler 給它,以響應 Http 請求。


    需要注意的是,無論是RpcService還是HttpHandler,註冊給RpcServer後,ownership就屬於RpcServer了,其生存期由RpcServer負責,你不能再去delete了。


RpcClient 

    該類僅用於客戶端。一個客戶端程序只需要一個RpcClient對象,其負責所有RpcChannel對象的管理和對服務器端應答的處理。


RpcChannel 

    該類僅用於客戶端。它代表通訊通道,每組服務器地址對應一個RpcChannel對象,客戶端通過它向服務器端發送方法調用請求並接收結果。Poppy內部以keepalive的方式來管理活動的連接,支持無狀態服務器的自動負載均衡。


    客戶端要發起調用,需要先以RpcClient*爲參數構造RpcChannel。


RpcController 

    該類既用於客戶端,也用於服務器端。它存儲一次RPC方法調用的上下文,包括對應的連接標識、該次調用的序列號以及方法執行結果。由於Poppy是全異步的,調用的序列號是爲了便於客戶端識別服務器的某個應答包對應具體哪次方法調用。每個活動的controller代表一次已經發出還未完成的調用。 在完成前,controller不能被用作其他用途;調用完成後,則可以用來發起下一次調用。


    RpcController的方法:


    Rpc調用發起之前可以調用的方法:


    void SetTimeout(int64_t timeout); // 設置期望超時,如果不是0,覆蓋proto裏的超時設置。


    Rpc調用發起之後可以調用的方法:


    bool Failed() const; // 返回上次調用是否失敗


    int ErrorCode() const; // 返回上次調用的錯誤碼,實際類型爲 RpcErrorCode


    std::string ErrorText() const; // 返回錯誤信息的文字描述


    服務器方可以調用的方法:


    int64_t Time() const; // 請求接收的時間


    int64_t Timeout() const; // 客戶端期望的超時


    void SetFailed(const std::string& reason); // 主動設置爲失敗


    更詳細的介紹可以閱讀Poppy文檔和範例。


文本協議 

    除了二進制協議外,Poppy支持還以普通的HTTP協議,傳輸以JSON/protobuf文本格式定義的消息。很方便用各種腳本語言調用,甚至用 bash,調 wget/curl 都能發起 RPC 調用。

多語言 



發佈了707 篇原創文章 · 獲贊 48 · 訪問量 97萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章