嵌入式RPC框架設(shè)計實(shí)踐:六大核心類構(gòu)建高效RPC框架

[TOC]

引言

? 在先前發(fā)布的文章中,我們構(gòu)建了RPC底層數(shù)據(jù)傳輸?shù)幕A(chǔ)設(shè)計并實(shí)現(xiàn)了其功能(詳盡代碼與深入分析可參閱《實(shí)戰(zhàn)高效RPC方案在嵌入式環(huán)境中的應(yīng)用與揭秘》)。本文將繼續(xù)以此為基礎(chǔ)秃流,探討如何通過分層封裝來提升RPC框架的易用性苟跪,旨在提供更便捷和正式的使用接口。

概述

? 在之前的文章中,我們闡述了結(jié)合共享內(nèi)存與環(huán)形緩沖區(qū)技術(shù)箍邮,設(shè)計并實(shí)現(xiàn)了一種創(chuàng)新的共享環(huán)形緩沖區(qū)機(jī)制棒动,用以支持RPC進(jìn)程間高效的數(shù)據(jù)請求與響應(yīng)交互糙申。本篇文章將進(jìn)一步依托此共享環(huán)形緩沖區(qū)的核心架構(gòu),專注于RPC框架的接口層次封裝船惨,力求精簡對外接口柜裸,減輕使用者負(fù)擔(dān),從而實(shí)現(xiàn)實(shí)現(xiàn)服務(wù)進(jìn)程與RPC框架之間的無縫集成與簡便應(yīng)用粱锐。

需求

? 針對專為嵌入式Linux環(huán)境定制的小型RPC框架疙挺,其核心功能需求可概括為以下幾點(diǎn),旨在實(shí)現(xiàn)高效怜浅、靈活且易于集成的遠(yuǎn)程通信解決方案:

  1. 自動服務(wù)注冊與發(fā)現(xiàn)
    框架應(yīng)在服務(wù)端啟動時自動完成RPC接口的注冊铐然,同時,客戶端需能動態(tài)識別并連接至可用服務(wù)端口恶座,無縫調(diào)用指定服務(wù)接口搀暑。
  2. 極致性能與低延遲通訊
    強(qiáng)調(diào)從客戶端請求發(fā)出到接收服務(wù)端響應(yīng)的全鏈路快速響應(yīng),確保過程無明顯阻塞跨琳,適合實(shí)時性要求高的嵌入式應(yīng)用自点。
  3. 泛型數(shù)據(jù)序列化支持
    支持豐富數(shù)據(jù)類型的參數(shù)傳遞,通過高效的序列化與反序列化機(jī)制脉让,保障各類數(shù)據(jù)在調(diào)用過程中的準(zhǔn)確無損傳輸樟氢。
  4. 高度抽象與易用性設(shè)計
    框架設(shè)計應(yīng)隱藏復(fù)雜的數(shù)據(jù)通信邏輯,為開發(fā)者提供簡潔直觀的API接口侠鳄,使得遠(yuǎn)程方法調(diào)用如同調(diào)用本地函數(shù)一樣直接和自然埠啃。

注:由于是針對嵌入式環(huán)境定制的RPC框架設(shè)計,重點(diǎn)聚焦于核心實(shí)用功能伟恶,鑒于資源限制與特定場景需求碴开,我們將注意力集中于以下基本需求,暫不涵蓋如數(shù)據(jù)加密等高級安全特性和跨語言交互能力。

類圖

? 針對上述需求的分析潦牛,以及RPC功能的理解眶掌。初步可將其分為6個類實(shí)現(xiàn):BindingHubBindInterface巴碗、Binder朴爬、IBinderParcel橡淆、SharedRingBuffer召噩。類圖如下:

RPC框架類圖
  • BindingHub (Bind中央?yún)f(xié)調(diào)器)
    該類核心職責(zé)在于維護(hù)一個全局的服務(wù)注冊表,其中記錄了所有服務(wù)端的遠(yuǎn)程調(diào)用元數(shù)據(jù)逸爵。其作為獨(dú)立的服務(wù)運(yùn)行在后臺具滴,扮演著信息維護(hù)的角色,向客戶端提供查詢服務(wù)端遠(yuǎn)程入口能力师倔。

  • SharedRingBuffer (數(shù)據(jù)傳輸類)
    構(gòu)成數(shù)據(jù)傳輸基礎(chǔ)設(shè)施的關(guān)鍵組件构韵,利用先進(jìn)的共享內(nèi)存技術(shù)結(jié)合高效的環(huán)形緩沖區(qū)設(shè)計,極大提升了進(jìn)程間數(shù)據(jù)傳輸?shù)乃俾逝c效率。此組件減少了系統(tǒng)調(diào)用的開銷,特別是在資源受限的嵌入式環(huán)境中铡溪,其輕量級特性和高性能表現(xiàn)尤為顯著。

  • Parcel (數(shù)據(jù)封裝類)
    該類是數(shù)據(jù)封裝與傳輸?shù)暮诵膶?shí)現(xiàn)显拳,負(fù)責(zé)數(shù)據(jù)的序列化與反序列化操作,確保信息在不同進(jìn)程間準(zhǔn)確無誤地流動抖单。通過集成SharedRingBuffer,它不僅高效地利用共享內(nèi)存與環(huán)形緩沖區(qū)技術(shù)完成數(shù)據(jù)交換遇八,還實(shí)現(xiàn)了數(shù)據(jù)交互的同步控制矛绘,增強(qiáng)了傳輸?shù)目煽啃耘c一致性。

  • Binder (服務(wù)端交互標(biāo)識)
    在服務(wù)端側(cè)刃永,該類扮演數(shù)據(jù)傳輸控制器的角色货矮,依據(jù)服務(wù)標(biāo)識生成對應(yīng)的rspParcelreqParcel實(shí)例與客戶端通信。此類接口封裝在BindInterface中斯够,服務(wù)側(cè)代碼無感知囚玫。

  • IBinder (客戶端交互標(biāo)識)
    在客戶端側(cè),負(fù)責(zé)依據(jù)服務(wù)名稱獲取與之相匹配的rspParcelreqParcel實(shí)例與服務(wù)端通信读规。此類接口封裝在BindInterface中抓督,客戶側(cè)代碼無感知。

  • BindInterface (Bind初始化接口類)
    此接口定義了RPC框架與外部應(yīng)用交互的邊界束亏,為應(yīng)用程序提供簡潔的初始化接口铃在。無論是服務(wù)端部署還是客戶端查詢適配,都通過此類返回類型安全的BinderIBinder實(shí)例。

源碼實(shí)現(xiàn)

編程環(huán)境

① 編譯環(huán)境: Linux環(huán)境
② 語言: C++語言

接口定義

  • BindingHub (Bind中央?yún)f(xié)調(diào)器)
class BindingHub
{
public:
    ~BindingHub();

    static BindingHub* GetInstance();
    int32_t HandleMsgLoop();

private:
    BindingHub();

    int32_t EnvReady(const std::string& srvName);
    int32_t MsgResponseAddService();
    int32_t MsgResponseRemoveService();
    int32_t MsgResponseGetService();

private:
    using HandleFunction = int32_t (BindingHub::*)(void);

    std::map<std::string, BinderInfo> mBinderMap;
    std::map<int32_t, HandleFunction> mHandleFuncs;
};

BindHub 維護(hù)了一個全局服務(wù)注冊表mBinderMap定铜,用于實(shí)現(xiàn)進(jìn)程間服務(wù)的高效發(fā)現(xiàn)與通信阳液。分為如下步驟描述:

  1. 步驟一:服務(wù)注冊過程
    初始化請求
    當(dāng)一個服務(wù)進(jìn)程欲將其服務(wù)注冊至系統(tǒng)中時,首先向BindHub發(fā)起注冊請求揣炕。請求中包含了服務(wù)的唯一標(biāo)識(進(jìn)程名)帘皿,作為服務(wù)辨識的基礎(chǔ)信息。
    分配唯一標(biāo)識
    接收到注冊請求后畸陡,BindHub 會隨即為該服務(wù)生成一個隨機(jī)的鹰溜、唯一的 key 值。用于為每個服務(wù)分配一個系統(tǒng)內(nèi)的代理標(biāo)識罩锐,便于后續(xù)的匿名化調(diào)用與管理奉狈。
    建立映射關(guān)系
    將生成的 key 與服務(wù)端進(jìn)程名綁定,緩存至 mBinderMap 中涩惑。此步驟用于確定服務(wù)名與key之間的綁定關(guān)系仁期,為后續(xù)查找與調(diào)用服務(wù)奠定了基礎(chǔ)。
    響應(yīng)確認(rèn)
    完成映射關(guān)系的建立后竭恬,BindHub 將生成的 key 與服務(wù)名一并返回給服務(wù)端進(jìn)程跛蛋。服務(wù)端進(jìn)程以key和服務(wù)名,創(chuàng)建共享內(nèi)存和信號量痊硕,作為通信憑證赊级,為即將到來的客戶端請求做好準(zhǔn)備。

  2. 步驟二:服務(wù)獲取與通信
    客戶端請求服務(wù)
    客戶端進(jìn)程啟動服務(wù)調(diào)用前岔绸,需先通過BindHub請求指定服務(wù)理逊。此請求中需包含服務(wù)的名稱。
    查詢服務(wù)信息
    接收到客戶端的請求后盒揉,BindHub 在其維護(hù)的 mBinderMap 中依據(jù)服務(wù)名進(jìn)行查找晋被,獲取與該服務(wù)關(guān)聯(lián)的唯一key
    返回通信憑證
    查詢到key后刚盈,BindHub 向客戶端返回該服務(wù)的 name 與對應(yīng)的 key羡洛。這兩個元素共同構(gòu)成了客戶端與服務(wù)端通信的憑證,允許客戶端直接且安全地與目標(biāo)服務(wù)建立連接藕漱。
    建立直接通信
    客戶端利用獲得的 namekey欲侮,可直接與服務(wù)端進(jìn)程建立點(diǎn)對點(diǎn)通信鏈路(共享內(nèi)存和信號量),無需再經(jīng)過 BindHub 中介肋联,從而實(shí)現(xiàn)高效的進(jìn)程間通信威蕉。

  • SharedRingBuffer (數(shù)據(jù)傳輸類)
    此類為共享環(huán)形緩沖區(qū),用于存儲通信數(shù)據(jù)橄仍,之前文章有詳細(xì)說明忘伞,這里不再細(xì)說。

  • Parcel (數(shù)據(jù)封裝類)
    此類用實(shí)現(xiàn)數(shù)據(jù)序列化和反序列化,然后通過SharedRingBuffer傳輸氓奈,業(yè)務(wù)比較簡單翘魄。

class Parcel
{
public:
    Parcel(const std::string& path, int key, bool master);
    ~Parcel();
    Parcel(const Parcel& other) = delete;
    Parcel& operator=(const Parcel& other) = delete;
    Parcel(Parcel&& other) = delete;
    Parcel& operator=(Parcel&& other) = delete;

    int Wait();
    int Post();
    int WriteBool(bool value);
    int ReadBool(bool& value);
    int WriteInt(int value);
    int ReadInt(int& value);
    int WriteString(const std::string& value);
    int ReadString(std::string& value);
    int WriteData(void* data, int size);
    int ReadData(void* data, int& size);
    
private:
    bool                mMaster;
    int                 mShmKey;
    sem_t*              mSem ;
    std::string         mShmPath;
    SharedRingBuffer*   mRingBuffer;
};
  • Binder (服務(wù)端交互標(biāo)識)
    ? 此類作用于服務(wù)端,通過服務(wù)名namekey生成兩個Parecl實(shí)例:reqParcelrspParcel舀奶。作為通信橋梁與客戶端進(jìn)行交互暑竟。
class Binder
{
public:
    Binder(const std::string& name, int key) : mKey(key), mName(name) {};
    ~Binder() {};

    int32_t GetParcel(std::shared_ptr<Parcel>& reqParcel, std::shared_ptr<Parcel>& rspParcel);

private:
    int32_t mKey;
    std::string mName;
};
  • IBinder (客戶端交互標(biāo)識)
    ? 此類作用于客戶端,作用和Binder相似育勺,只是生成的是用于與服務(wù)端交互的兩個Parcel實(shí)例但荤。
    ? 客戶端的reqParecel負(fù)責(zé)向服務(wù)端rspParcel發(fā)數(shù)據(jù),客戶端的rspParcel負(fù)責(zé)接收服務(wù)端reqParecel的應(yīng)答消息涧至。從而實(shí)現(xiàn)客戶端與服務(wù)端的全雙工通信腹躁。
class IBinder
{
public:
    IBinder(const std::string& name, int key) : mKey(key), mName(name) {};
    ~IBinder() {};

    int32_t GetParcel(std::shared_ptr<Parcel>& reqParcel, std::shared_ptr<Parcel>& rspParcel);

private:
    int mKey;
    std::string mName;
};
  • BindInterface (Bind初始化接口類)
    ? 該類的作用在于抽象化BinderIBinder接口的復(fù)雜性,用戶僅需通過初始化方法Initialize即可輕松獲得預(yù)配置的Parcel實(shí)例南蓬,進(jìn)而開展數(shù)據(jù)交換操作纺非。
class BindInterface
{
public:
    ~BindInterface() = default;

    static BindInterface* GetInstance();
    bool InitializeServiceBinder(const std::string& srvName, std::shared_ptr<Parcel>& pReqParcel, std::shared_ptr<Parcel>& pRspParcel);
    bool InitializeClientBinder(const std::string& srvName, std::shared_ptr<Parcel>& pReqParcel, std::shared_ptr<Parcel>& pRspParcel);

private:
    BindInterface() = default;
    std::shared_ptr<Binder>  AddService(const std::string& name);
    std::shared_ptr<IBinder> GetService(const std::string& name);
    int32_t RemoveService(const std::string& name);
};

測試驗(yàn)證

  • 測試代碼
    篇幅有限,這里只列舉關(guān)鍵部分代碼:
int Client()
{

    std::shared_ptr<Parcel> pReqParcel = nullptr;
    std::shared_ptr<Parcel> pRspParcel = nullptr;

    BindInterface::GetInstance()->InitializeClientBinder(SERVICE_NAME, pReqParcel, pRspParcel);
    if (pReqParcel == nullptr || pRspParcel == nullptr) {
        SPR_LOGE("GetParcel failed!\n");
        return -1;
    }

...
    pReqParcel->WriteInt(CMD_SUM);
    pReqParcel->WriteInt(10);
    pReqParcel->WriteInt(20);
    pReqParcel->Post();

    int sum = 0, ret = 0;
    pRspParcel->Wait();
    pRspParcel->ReadInt(sum);
    pRspParcel->ReadInt(ret);
    SPR_LOGD("sum = %d, ret = %d\n", sum, ret);
...
}

...
int Server()
{
    std::shared_ptr<Parcel> pReqParcel = nullptr;
    std::shared_ptr<Parcel> pRspParcel = nullptr;

    BindInterface::GetInstance()->InitializeServiceBinder(SERVICE_NAME, pReqParcel, pRspParcel);
    if (pReqParcel == nullptr || pRspParcel == nullptr) {
        SPR_LOGE("GetParcel failed\n");
        return -1;
    }

    do {
        int cmd = 0;
        pReqParcel->Wait();
        pReqParcel->ReadInt(cmd);
        switch(cmd)
        {
            case CMD_SUM:
            {
                SPR_LOGD("CMD_SUM\n");
                int a = 0, b = 0;
                pReqParcel->ReadInt(a);
                pReqParcel->ReadInt(b);

                int sum = a + b;
                pRspParcel->WriteInt(sum);
                pRspParcel->WriteInt(0);
                pRspParcel->Post();
                break;
            }

            default:
            {
                SPR_LOGE("Unknown cmd: %d\n", cmd);
                break;
            }
        }

    } while(1);

    return 0;
}
  • 測試結(jié)果
$ ./debugbinder
------------------------------------------------------------------
Usage:
0: CMD_TEST
1: CMD_SUM
q: Quit
------------------------------------------------------------------
146 DebugBinder D: Input:1
173 DebugBinder D: sum = 30, ret = 0

這里只是一個簡單測試赘方,客戶端發(fā)起請求烧颖,并同步獲取服務(wù)端返回值30,初步驗(yàn)證RPC功能OK窄陡。

總結(jié)

  • 先前探討了SharedRingbuffer在數(shù)據(jù)傳輸中的低層機(jī)制炕淮。本文章重心主要聚焦軟件設(shè)計,旨在簡化RPC框架的使用體驗(yàn)跳夭,提升易用性涂圆。

  • RPC通信核心在于參數(shù)傳遞與返回值的高效同步。該過程依托共享內(nèi)存與信號量技術(shù)币叹,確保數(shù)據(jù)流暢傳輸與操作同步润歉,提升交互效率。

  • RPC框架的實(shí)踐不僅為項(xiàng)目提供便捷工具套硼,也能夠深入了解RPC技術(shù)底層邏輯與原理卡辰,從技術(shù)和設(shè)計的層面提升自己胞皱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邪意,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子反砌,更是在濱河造成了極大的恐慌雾鬼,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宴树,死亡現(xiàn)場離奇詭異策菜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門又憨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翠霍,“玉大人,你說我怎么就攤上這事蠢莺『祝” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵躏将,是天一觀的道長锄弱。 經(jīng)常有香客問我,道長祸憋,這世上最難降的妖魔是什么会宪? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蚯窥,結(jié)果婚禮上掸鹅,老公的妹妹穿的比我還像新娘。我一直安慰自己沟沙,他們只是感情好河劝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著矛紫,像睡著了一般赎瞎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颊咬,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天务甥,我揣著相機(jī)與錄音,去河邊找鬼喳篇。 笑死敞临,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的麸澜。 我是一名探鬼主播挺尿,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼炊邦!你這毒婦竟也來了编矾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤馁害,失蹤者是張志新(化名)和其女友劉穎窄俏,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碘菜,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凹蜈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年限寞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仰坦。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡履植,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悄晃,到底是詐尸還是另有隱情静尼,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布传泊,位于F島的核電站鼠渺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏眷细。R本人自食惡果不足惜拦盹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望溪椎。 院中可真熱鬧普舆,春花似錦、人聲如沸校读。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽歉秫。三九已至蛾洛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雁芙,已是汗流浹背轧膘。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留兔甘,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓洞焙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親澡匪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容