嵌入式RPC框架设计实践:六大核心类构建高效RPC框架

2024-04-27  本文已影响0人  拂去尘世尘

[TOC]

引言

  在先前发布的文章中,我们构建了RPC底层数据传输的基础设计并实现了其功能(详尽代码与深入分析可参阅《实战高效RPC方案在嵌入式环境中的应用与揭秘》)。本文将继续以此为基础,探讨如何通过分层封装来提升RPC框架的易用性,旨在提供更便捷和正式的使用接口。

概述

  在之前的文章中,我们阐述了结合共享内存与环形缓冲区技术,设计并实现了一种创新的共享环形缓冲区机制,用以支持RPC进程间高效的数据请求与响应交互。本篇文章将进一步依托此共享环形缓冲区的核心架构,专注于RPC框架的接口层次封装,力求精简对外接口,减轻使用者负担,从而实现实现服务进程与RPC框架之间的无缝集成与简便应用。

需求

  针对专为嵌入式Linux环境定制的小型RPC框架,其核心功能需求可概括为以下几点,旨在实现高效、灵活且易于集成的远程通信解决方案:

  1. 自动服务注册与发现
    框架应在服务端启动时自动完成RPC接口的注册,同时,客户端需能动态识别并连接至可用服务端口,无缝调用指定服务接口。
  2. 极致性能与低延迟通讯
    强调从客户端请求发出到接收服务端响应的全链路快速响应,确保过程无明显阻塞,适合实时性要求高的嵌入式应用。
  3. 泛型数据序列化支持
    支持丰富数据类型的参数传递,通过高效的序列化与反序列化机制,保障各类数据在调用过程中的准确无损传输。
  4. 高度抽象与易用性设计
    框架设计应隐藏复杂的数据通信逻辑,为开发者提供简洁直观的API接口,使得远程方法调用如同调用本地函数一样直接和自然。

注:由于是针对嵌入式环境定制的RPC框架设计,重点聚焦于核心实用功能,鉴于资源限制与特定场景需求,我们将注意力集中于以下基本需求,暂不涵盖如数据加密等高级安全特性和跨语言交互能力。

类图

  针对上述需求的分析,以及RPC功能的理解。初步可将其分为6个类实现:BindingHubBindInterfaceBinderIBinderParcelSharedRingBuffer。类图如下:

RPC框架类图

源码实现

编程环境

① 编译环境: Linux环境
② 语言: C++语言

接口定义

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 维护了一个全局服务注册表mBinderMap,用于实现进程间服务的高效发现与通信。分为如下步骤描述:

  1. 步骤一:服务注册过程
    初始化请求
    当一个服务进程欲将其服务注册至系统中时,首先向BindHub发起注册请求。请求中包含了服务的唯一标识(进程名),作为服务辨识的基础信息。
    分配唯一标识
    接收到注册请求后,BindHub 会随即为该服务生成一个随机的、唯一的 key 值。用于为每个服务分配一个系统内的代理标识,便于后续的匿名化调用与管理。
    建立映射关系
    将生成的 key 与服务端进程名绑定,缓存至 mBinderMap 中。此步骤用于确定服务名与key之间的绑定关系,为后续查找与调用服务奠定了基础。
    响应确认
    完成映射关系的建立后,BindHub 将生成的 key 与服务名一并返回给服务端进程。服务端进程以key和服务名,创建共享内存和信号量,作为通信凭证,为即将到来的客户端请求做好准备。

  2. 步骤二:服务获取与通信
    客户端请求服务
    客户端进程启动服务调用前,需先通过BindHub请求指定服务。此请求中需包含服务的名称。
    查询服务信息
    接收到客户端的请求后,BindHub 在其维护的 mBinderMap 中依据服务名进行查找,获取与该服务关联的唯一key
    返回通信凭证
    查询到key后,BindHub 向客户端返回该服务的 name 与对应的 key。这两个元素共同构成了客户端与服务端通信的凭证,允许客户端直接且安全地与目标服务建立连接。
    建立直接通信
    客户端利用获得的 namekey,可直接与服务端进程建立点对点通信链路(共享内存和信号量),无需再经过 BindHub 中介,从而实现高效的进程间通信。

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;
};
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;
};
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;
};
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);
};

测试验证

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;
}
$ ./debugbinder
------------------------------------------------------------------
Usage:
0: CMD_TEST
1: CMD_SUM
q: Quit
------------------------------------------------------------------
146 DebugBinder D: Input:1
173 DebugBinder D: sum = 30, ret = 0

这里只是一个简单测试,客户端发起请求,并同步获取服务端返回值30,初步验证RPC功能OK。

总结

上一篇下一篇

猜你喜欢

热点阅读