[经验分享] 【SUBJECT技术】OpenHarmony IPC 之ServiceAbility应用(系统架构) 原创

诚迈_雨哥 显示全部楼层 发表于 2024-6-1 16:29:05

一、重要性

OpenHarmony IPC 之ServiceAbility应用极其广泛。例如在OpenHarmony以下四个目录共找到约1087个子系统用到了这种机制,其重要性可见一斑。 grep -nr "::OnRemoteRequest" ./foundation/ ./base/ ./drivers/ ./kernel/ 调试时OnRemoteRequest()函数是必经之路,服务端函数有没有调起就在这里“打桩”或输出日志。

Client-Server软件模型外加Interface接口共同组成了OpenHarmony诸多子系统的架构,使其系统的稳定性、扩展性以及进程通信能力得到加强。子系统中均可见到Interface接口目录、Framework客户端目录以及Service服务端目录,工作重点一般集中在Service服务端,如下图所示。

clientserver2.PNG

这种软件架构提供了丰富的软件接口,又提供了灵活的接口组织策略,很容易将一个子系统与另一个子系统相连接,形成一个有机的整体。典型的通路是Foundation→Base→Drivers(Third_paty)

interface_proxy_stub.PNG

传统紧凑的软件架构因扩展性、稳定性饱受诟病,与其形成了鲜明对比,如下图:

interface_implement.PNG

说明 以下部分内容来自参考链接,他们已经整理的很好了,此处是借花献佛!

二、IPC 介绍

IPC(Inter-Process Communication)

IPC是用于进程间通信的技术,指的是进程间的数据交互过程。它包括各种形式的消息传递,共享资源,以及同步对象,如互斥量等,以确保安全的并发访问共享资源。IPC通常使用Binder驱动,主要用于设备内的跨进程通信,如OpenHarmony系统中的进程间通信。

IPC与RPC(Remote Procedure Call)机制用于实现跨进程通信,不同的是前者使用Binder驱动,用于设备内的跨进程通信,而后者使用软总线驱动,用于跨设备跨进程通信。IPC和RPC通常采用客户端-服务器(Client-Server)模型,服务请求方(Client)可获取提供服务提供方(Server)的代理 (Proxy),并通过此代理读写数据来实现进程间的数据通信。通常,系统能力(System Ability)Server侧会先注册到系统能力管理者(System Ability Manager,缩写SAMgr)中,SAMgr负责管理这些SA并向Client提供相关的接口。Client要和某个具体的SA通信,必须先从SAMgr中获取该SA的代理,然后使用代理和SA通信。三方应用可以使用FA提供的接口绑定服务提供方的Ability,获取代理,进行通信。下文使用Proxy表示服务请求方,Stub表示服务提供方。

总结来说,IPC是进程间通信的方式,侧重于进程内的数据共享和资源管理。IPC通信机制架构图如下:

aa.PNG

IPC代码目录

b.png

约束

  • 单个设备上跨进程通信时,传输的数据量最大约为1MB,过大的数据量请使用匿名共享内存。
  • 不支持在RPC中订阅匿名Stub对象(没有向SAMgr注册Stub对象)的死亡通知。
  • 不支持把跨设备的Proxy对象传递回该Proxy对象所指向的Stub对象所在的设备,即指向远端设备Stub的Proxy对象不能在本设备内进行二次跨进程传递。

三、ServiceAbility应用

编译依赖

sdk依赖:

external_deps = [
  "ipc:ipc_core",
]

此外, IPC/RPC依赖的refbase实现在公共基础库下,请增加对utils的依赖:

external_deps = [
  "c_utils:utils",
]

说明 通过SA的标识和设备NetworkId,从SAMgr获取Proxy,通过Proxy实现与Stub的跨进程通信。

实现跨进程通信的基本步骤:

  1. 定义接口类 接口类继承IRemoteBroker,定义描述符、业务函数和消息码。
  2. 实现服务提供端(Stub) Stub继承IRemoteStub(Native),除了接口类中未实现方法外,还需要实现AsObject方法及OnRemoteRequest方法。
  3. 实现服务请求端(Proxy) Proxy继承IRemoteProxy(Native),封装业务函数,调用SendRequest将请求发送到Stub。
  4. 注册SA 服务提供方所在进程启动后,申请SA的唯一标识,将Stub注册到SAMgr。
  5. 获取SA 通过SA的标识和设备NetworkId,从SAMgr获取Proxy,通过Proxy实现与Stub的跨进程通信。

IPC接口说明

类/接口 方法 功能说明
IRemoteBroker sptr<IRemoteObject> AsObject() 返回通信对象。Stub端返回RemoteObject对象本身,Proxy端返回代理对象。
IRemoteStub virtual int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) 请求处理方法,派生类需要重写该方法用来处理Proxy的请求并返回结果。
IRemoteProxy Remote()->SendRequest(code, data, reply, option) 消息发送方法,业务的Proxy类需要从IRemoteProxy类派生,该方法用来向对端发送消息。

使用说明

1. 定义IPC接口ITestAbility

IPC接口继承IPC基类接口IRemoteBroker,接口里定义描述符、业务函数和消息码,其中业务函数在Proxy端和Stub端都需要实现。

class ITestAbility : public IRemoteBroker {
public:
    // DECLARE_INTERFACE_DESCRIPTOR是必须的, 入参需使用std::u16string;
    DECLARE_INTERFACE_DESCRIPTOR(u"test.ITestAbility"); // DESCRIPTOR接口描述符建议使用"组件名.类名"的格式
    int TRANS_ID_PING_ABILITY = 1; // 定义消息码
    virtual int TestPingAbility(const std::u16string &dummy) = 0; // 定义业务函数
};

2. 定义和实现服务端TestAbilityStub

该类是和IPC框架相关的实现,需要继承自IRemoteStub<ITestAbility>。Stub端作为接收请求的一端,需重写OnRemoteRequest方法用于接收客户端调用。

class TestAbilityStub : public IRemoteStub<ITestAbility> {
public:
    virtual int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) override;
    int TestPingAbility(const std::u16string &dummy) override;
};

int TestServiceStub::OnRemoteRequest(uint32_t code,
    MessageParcel &data, MessageParcel &reply, MessageOption &option)
{
    if (data.ReadInterfaceToken() != GetDescriptor()) { //校验是否为本服务的接口描述符,避免中继攻击
        return -1;
    }
    switch (code) {
        case TRANS_ID_PING_ABILITY: {
            std::u16string dummy = data.ReadString16();
            int result = TestPingAbility(dummy);
            reply.WriteInt32(result);
            return 0;
        }
        default:
            return IPCObjectStub::OnRemoteRequest(code, data, reply, option);
    }
}

3. 定义服务端业务函数具体实现类TestAbility

class TestAbility : public TestAbilityStub {
public:
    int TestPingAbility(const std::u16string &dummy);
}

int TestAbility::TestPingAbility(const std::u16string &dummy) {
    return 0;
}

4. 定义和实现客户端TestAbilityProxy

该类是Proxy端实现,继承自IRemoteProxy<ITestAbility>,调用SendRequest接口向Stub端发送请求,对外暴露服务端提供的能力。

class TestAbilityProxy : public IRemoteProxy<ITestAbility> {
public:
    explicit TestAbilityProxy(const sptr<IRemoteObject> &impl);
    int TestPingService(const std::u16string &dummy) override;
private:
    static inline BrokerDelegator<TestAbilityProxy> delegator_; // 方便使用iface_cast宏
}

TestAbilityProxy::TestAbilityProxy(const sptr<IRemoteObject> &impl)
    : IRemoteProxy<ITestAbility>(impl)
{
}

int TestAbilityProxy::TestPingService(const std::u16string &dummy) {
    MessageOption option;
    MessageParcel dataParcel, replyParcel;
    if(!dataParcel.WriteInterfaceToken(GetDescriptor())) { //所有对外接口的proxy实现都要写入接口描述符,用于stub端检验
        return -1;
    }
    if(!dataParcel.WriteString16(dummy)) {
        return -1;
    }
    int error = Remote()->SendRequest(TRANS_ID_PING_ABILITY, dataParcel, replyParcel, option);
    int result = (error == ERR_NONE) ? replyParcel.ReadInt32() : -1;
    return result;
}

5. 同步调用与异步调用

MessageOption作为发送接口(原型如下)的入参,可设定同步(TF_SYNC)、异步(TF_ASYNC),默认情况下设定为同步,其余可通过MessageOption构造方法或void SetFlags(int flags)设定。

    int SendRequest(uint32_t code, MessageParcel &data,
        MessageParcel &reply, MessageOption &option) override;
    MessageOption option;
    option.setFlags(option.TF_ASYNC);

6. SA注册与启动

SA需要将自己的TestAbilityStub实例通过AddSystemAbility接口注册到SystemAbilityManager,设备内与分布式的注册参数不同。

// 注册到本设备内
auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
samgr->AddSystemAbility(said, new TestAbility());

// 在组网场景下,会被同步到其他设备上
auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
ISystemAbilityManager::SAExtraProp saExtra;
saExtra.isDistributed = true; // 设置为分布式SA
int result = samgr->AddSystemAbility(said, new TestAbility(), saExtra);

7. SA获取与调用

通过SystemAbilityManager的GetSystemAbility方法可获取到对应SA的代理IRemoteObject,然后构造TestAbilityProxy即可。

// 获取本设备内注册的SA的proxy
sptr<ISystemAbilityManager> samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
sptr<IRemoteObject> remoteObject = samgr->GetSystemAbility(said);
sptr<ITestAbility> testAbility = iface_cast<ITestAbility>(remoteObject); // 使用iface_cast宏转换成具体类型

// 获取其他设备注册的SA的Proxy
sptr<ISystemAbilityManager> samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
sptr<IRemoteObject> remoteObject = samgr->GetSystemAbility(sdid, deviceId); // deviceId是指定设备的标识符
sptr<TestAbilityProxy> proxy(new TestAbilityProxy(remoteObject)); // 直接构造具体Proxy

参考链接

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部