OpenHarmony IPC通讯详解

简介

        IPC(Inter-Process Communication)与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表示服务提供方。

系统架构

 图 1 IPC通信机制架构图

目录

约束

  1. 单个设备上跨进程通信时,传输的数据量最大约为1MB,过大的数据量请使用匿名共享内存。
  2. 不支持把跨设备的Proxy对象传递回该Proxy对象所指向的Stub对象所在的设备。

编译构建

JS侧依赖

import rpc from "@ohos.rpc"
import featureAbility from "@ohos.ability.featureAbility"

Native侧编译依赖

sdk依赖:

external_deps = [
  "ipc:ipc_core",
]

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

external_deps = [
  "c_utils:utils",
]

说明

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

  1. 获取代理

    使用ohos.ability.featureAbility提供的connectAbility方法绑定Ability,在参数里指定要绑定的Ability所在应用的包名、组件名,如果是跨设备的情况,还需要指定所在设备的NetworkId。用户需要在服务端的onConnect方法里返回一个继承自ohos.rpc.RemoteObject的对象,此对象会在其onRemoteMessageRequest方法里接收到请求。

  2. 发送请求

    客户端在connectAbility参数指定的回调函数接收到代理对象后,使用ohos.rpc模块提供的方法完成RPC通信,其中MessageParcel提供了读写各种类型数据的方法,IRemoteObject提供了发送请求的方法,RemoteObject提供了处理请求的方法onRemoteRequest,用户需要重写。

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

  1. 定义接口类

    接口类继承IRemoteBroker,定义描述符、业务函数和消息码。

  2. 实现服务提供端(Stub)

    Stub继承IRemoteStub(Native),除了接口类中未实现方法外,还需要实现AsObject方法及OnRemoteRequest方法。

  3. 实现服务请求端(Proxy)

    Proxy继承IRemoteProxy(Native),封装业务函数,调用SendRequest将请求发送到Stub。

  4. 注册SA

    服务提供方所在进程启动后,申请SA的唯一标识,将Stub注册到SAMgr。

  5. 获取SA

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

接口说明

表 1 JS侧IPC关键API

表 2 Native侧IPC接口 

 

使用说明

JS侧使用说明

  1. 客户端构造变量want,指定要绑定的Ability所在应用的包名、组件名,如果是跨设备的场景,还需要目标设备NetworkId。构造变量connect,指定绑定成功、绑定失败、断开连接时的回调函数。使用featureAbility提供的接口绑定Ability。

import rpc from "@ohos.rpc"
import featureAbility from "@ohos.ability.featureAbility"

let proxy = null
let connectId = null

// 单个设备
let want = {
    // 包名和组件名写实际的值
    "bundleName": "ohos.rpc.test.server",
    "abilityName": "ohos.rpc.test.server.ServiceAbility",
}
let connect = {
    onConnect:function(elementName, remote) {
        proxy = remote
    },
    onDisconnect:function(elementName) {
    },
    onFailed:function() {
        proxy = null
    }
}
connectId = featureAbility.connectAbility(want, connect)

// 如果是跨设备绑定,可以使用deviceManager获取目标设备NetworkId
import deviceManager from '@ohos.distributedHardware.deviceManager'
function deviceManagerCallback(deviceManager) {
    let deviceList = deviceManager.getTrustedDeviceListSync()
    let deviceId = deviceList[0].deviceId
    let want = {
        "bundleName": "ohos.rpc.test.server",
        "abilityName": "ohos.rpc.test.service.ServiceAbility",
        "deviceId": deviceId,
        "flags": 256
    }
    connectId = featureAbility.connectAbility(want, connect)
}
// 第一个参数是本应用的包名,第二个参数是接收deviceManager的回调函数
deviceManager.createDeviceManager("ohos.rpc.test", deviceManagerCallback)

2. 服务端被绑定的Ability在onConnect方法里返回继承自rpc.RemoteObject的对象,该对象需要实现onRemoteMessageRequest方法,处理客户端的请求。 

import rpc from "@ohos.rpc"
onConnect(want: Want) {
    var robj:rpc.RemoteObject = new Stub("rpcTestAbility")
    return robj
}
class Stub extends rpc.RemoteObject {
    constructor(descriptor) {
        super(descriptor)
    }
    onRemoteMessageRequest(code, data, reply, option) {
        // 根据code处理客户端的请求
        return true
    }
}

3. 客户端在onConnect回调里接收到代理对象,调用sendRequestAsync方法发起请求,在期约或者回调函数里接收结果。

import rpc from "@ohos.rpc"
// 使用期约
let option = new rpc.MessageOption()
let data = rpc.MessageParcel.create()
let reply = rpc.MessageParcel.create()
// 往data里写入参数
proxy.sendRequestAsync(1, data, reply, option)
    .then(function(result) {
        if (result.errCode != 0) {
            console.error("send request failed, errCode: " + result.errCode)
            return
        }
        // 从result.reply里读取结果
    })
    .catch(function(e) {
        console.error("send request got exception: " + e)
    }
    .finally(() => {
        data.reclaim()
        reply.reclaim()
    })

// 使用回调函数
function sendRequestCallback(result) {
    try {
        if (result.errCode != 0) {
            console.error("send request failed, errCode: " + result.errCode)
            return
        }
        // 从result.reply里读取结果
    } finally {
        result.data.reclaim()
        result.reply.reclaim()
    }
}
let option = new rpc.MessageOption()
let data = rpc.MessageParcel.create()
let reply = rpc.MessageParcel.create()
// 往data里写入参数
proxy.sendRequest(1, data, reply, option, sendRequestCallback)

4.IPC通信结束后,使用featureAbility的接口断开连接。

import rpc from "@ohos.rpc"
import featureAbility from "@ohos.ability.featureAbility"
function disconnectCallback() {
    console.info("disconnect ability done")
}
featureAbility.disconnectAbility(connectId, disconnectCallback)

Native侧使用说明

  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

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
OpenHarmonyIPC(Inter-Process Communication,进程间通信)主要有以下几种方式: 1. Binder IPC Binder IPC 是一种基于驱动程序的 IPC 方式,是 Android 系统中使用最广泛的 IPC 方式之一。在 OpenHarmony 中,Binder IPC 也被广泛应用于进程间通信,例如应用程序与服务之间的通信、进程间共享数据等。 2. Pipe IPC Pipe IPC 是一种基于文件描述符的 IPC 方式,它可以用于在两个进程之间传递数据。在 OpenHarmony 中,Pipe IPC 可以使用系统调用 pipe、mkfifo、open 等来创建管道,通过读写管道实现进程间通信。 3. Message Queue IPC Message Queue IPC 是一种基于消息的 IPC 方式,它可以用于在两个进程之间传递消息。在 OpenHarmony 中,Message Queue IPC 可以使用系统调用 msgget、msgsnd、msgrcv 等来创建消息队列,通过向消息队列发送和接收消息实现进程间通信。 4. Shared Memory IPC Shared Memory IPC 是一种基于共享内存的 IPC 方式,它可以在多个进程之间共享内存数据。在 OpenHarmony 中,Shared Memory IPC 可以使用系统调用 shmget、shmat、shmdt 等来创建共享内存区域,通过读写共享内存区域实现进程间通信。 5. Socket IPC Socket IPC 是一种基于网络IPC 方式,它可以在不同的计算机之间传递数据。在 OpenHarmony 中,Socket IPC 可以使用系统调用 socket、connect、send、recv 等来创建和使用套接字,通过套接字传递数据实现进程间通信。 这些 IPC 方式在 OpenHarmony 中都有广泛的应用,可以根据具体的应用场景选择最合适的 IPC 方式来实现进程间通信。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大王算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值