在先前发布的文章中,我们构建了RPC底层数据传输的基础设计并实现了其功能(详尽代码与深入分析可参阅《实战高效RPC方案在嵌入式环境中的应用与揭秘》)。本文将继续以此为基础,探讨如何通过分层封装来提升RPC框架的易用性,旨在提供更便捷和正式的使用接口。
文末有完整项目代码获取方式。另外,公众号文章留言功能现已开放。在阅读完每一篇文章后,欢迎随时于文末留言区留下您的想法、问题或反馈,我将积极关注并与您进行深入交流。
在之前的文章中,我们阐述了结合共享内存与环形缓冲区技术,设计并实现了一种创新的共享环形缓冲区机制,用以支持RPC进程间高效的数据请求与响应交互。本篇文章将进一步依托此共享环形缓冲区的核心架构,专注于RPC框架的接口层次封装,力求精简对外接口,减轻使用者负担,从而实现实现服务进程与RPC框架之间的无缝集成与简便应用。
针对专为嵌入式Linux环境定制的小型RPC框架,其核心功能需求可概括为以下几点,旨在实现高效、灵活且易于集成的远程通信解决方案:
注:由于是针对嵌入式环境定制的RPC框架设计,重点聚焦于核心实用功能,鉴于资源限制与特定场景需求,主要将注意力集中于以上基本需求,暂不涵盖如数据加密等高级安全特性和跨语言交互能力。
针对上述需求的分析,以及RPC功能的理解。初步可将其分为6个类实现:BindingHub
、BindInterface
、Binder
、IBinder
、Parcel
、SharedRingBuffer
。类图如下:
RPC框架类图
SharedRingBuffer
,它不仅高效地利用共享内存与环形缓冲区技术完成数据交换,还实现了数据交互的同步控制,增强了传输的可靠性与一致性。rspParcel
和reqParcel
实例与客户端通信。此类接口封装在BindInterface
中,服务侧代码无感知。rspParcel
和reqParcel
实例与服务端通信。此类接口封装在BindInterface
中,客户侧代码无感知。Binder
与IBinder
实例。编程环境
① 编译环境: 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
,用于实现进程间服务的高效发现与通信。分为如下步骤描述:
BindHub
发起注册请求。请求中包含了服务的唯一标识(进程名),作为服务辨识的基础信息。
② 分配唯一标识:
接收到注册请求后,BindHub
会随即为该服务生成一个随机的、唯一的 key
值。用于为每个服务分配一个系统内的代理标识,便于后续的匿名化调用与管理。
③ 建立映射关系:
将生成的 key
与服务端进程名绑定,缓存至 mBinderMap
中。此步骤用于确定服务名与key
之间的绑定关系,为后续查找与调用服务奠定了基础。
④ 响应确认:
完成映射关系的建立后,BindHub
将生成的 key
与服务名一并返回给服务端进程。服务端进程以key
和服务名,创建共享内存和信号量,作为通信凭证,为即将到来的客户端请求做好准备。BindHub
请求指定服务。此请求中需包含服务的名称。
② 查询服务信息:
接收到客户端的请求后,BindHub
在其维护的 mBinderMap
中依据服务名进行查找,获取与该服务关联的唯一key
。
③ 返回通信凭证:
查询到key
后,BindHub
向客户端返回该服务的 name
与对应的 key
。这两个元素共同构成了客户端与服务端通信的凭证,允许客户端直接且安全地与目标服务建立连接。
④ 建立直接通信:
客户端利用获得的 name
和 key
,可直接与服务端进程建立点对点通信链路(共享内存和信号量),无需再经过 BindHub
中介,从而实现高效的进程间通信。过SharedRingBuffer
传输,业务比较简单。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;
};
name
和key
生成两个Parecl
实例:reqParcel
和rspParcel
。作为通信桥梁与客户端进行交互。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;
};
Binder
相似,只是生成的是用于与服务端交互的两个Parcel
实例。
客户端的reqParecel
负责向服务端rspParcel
发数据,客户端的rspParcel
负责接收服务端reqParecel
的应答消息。从而实现客户端与服务端的全双工通信。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;
};
Binder
和IBinder
接口的复杂性,用户仅需通过初始化方法Initialize
即可轻松获得预配置的Parcel实例,进而开展数据交换操作。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。更复杂的使用示例,可通过文末拉取完整项目代码。
SharedRingbuffer
在数据传输中的低层机制。本文章重心主要聚焦软件设计,旨在简化RPC框架的使用体验,提升易用性。用心感悟,认真记录,写好每一篇文章,分享每一框干货。