一、IPC與RPC通信概述
基本概念
IPC(Inter-Process Communication)與RPC(Remote Procedure Call)用于實現跨進程通信,不同的是前者使用Binder驅動,用于設備內的跨進程通信,后者使用軟總線驅動,用于跨設備跨進程通信。需要跨進程通信的原因是因為每個進程都有自己獨立的資源和內存空間,其他進程不能隨意訪問不同進程的內存和資源,IPC/RPC便是為了突破這一點。IPC和RPC通常采用客戶端-服務器(Client-Server)模型,在使用時,請求服務的(Client)一端進程可獲取提供服務(Server)一端所在進程的代理(Proxy),并通過此代理讀寫數據來實現進程間的數據通信,更具體的講,首先請求服務的(Client)一端會建立一個服務提供端(Server)的代理對象,這個代理對象具備和服務提供端(Server)一樣的功能,若想訪問服務提供端(Server)中的某一個方法,只需訪問代理對象中對應的方法即可,代理對象會將請求發送給服務提供端(Server);然后服務提供端(Server)處理接受到的請求,處理完之后通過驅動返回處理結果給代理對象;最后代理對象將請求結果進一步返回給請求服務端(Client)。通常,Server會先注冊系統能力(System Ability)到系統能力管理者(System Ability Manager,縮寫SAMgr)中,SAMgr負責管理這些SA并向Client提供相關的接口。Client要和某個具體的SA通信,必須先從SAMgr中獲取該SA的代理,然后使用代理和SA通信。下文直接使用Proxy表示服務請求方,Stub表示服務提供方。


約束與限制
? ● 單個設備上跨進程通信時,傳輸的數據量最大約為1MB,過大的數據量請使用匿名共享內存
? ● 不支持在RPC中訂閱匿名Stub對象(沒有向SAMgr注冊Stub對象)的死亡通知。
? ● 不支持把跨設備的Proxy對象傳遞回該Proxy對象所指向的Stub對象所在的設備,即指向遠端設備Stub的Proxy對象不能在本設備內進行二次跨進程傳遞。
使用建議
首先,需要編寫接口類,接口類中必須定義消息碼,供通信雙方標識操作,可以有未實現的的方法,因為通信雙方均需繼承該接口類且雙方不能是抽象類,所以此時定義的未實現的方法必須在雙方繼承時給出實現,這保證了繼承雙方不是抽象類。然后,需要編寫Stub端相關類及其接口,并且實現AsObject方法及OnRemoteRequest方法。同時,也需要編寫Proxy端,實現接口類中的方法和AsObject方法,也可以封裝一些額外的方法用于調用SendRequest向對端發送數據。以上三者都具備后,便可以向SAMgr注冊SA了,此時的注冊應該在Stub所在進程完成。最后,在需要的地方從SAMgr中獲取Proxy,便可通過Proxy實現與Stub的跨進程通信了。
相關步驟:
? ● 實現接口類:需繼承IRemoteBroker,需定義消息碼,可聲明不在此類實現的方法。
? ● 實現服務提供端(Stub):需繼承IRemoteStub或者RemoteObject,需重寫AsObject方法及OnRemoteRequest方法。
? ● 實現服務請求端(Proxy):需繼承IRemoteProxy或RemoteProxy,需重寫AsObject方法,封裝所需方法調用SendRequest。
? ● 注冊SA:申請SA的唯一ID,向SAMgr注冊SA。
? ● 獲取SA:通過SA的ID和設備ID獲取Proxy,使用Proxy與遠端通信
二、 IPC與RPC通信開發指導
場景介紹
IPC/RPC的主要工作是讓運行在不同進程的Proxy和Stub互相通信,包括Proxy和Stub運行在不同設備的情況。
接口說明
表1 Native側IPC接口
| 類/接口 | 方法 | 功能說明 |
|---|---|---|
| IRemoteBroker | sptr 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類派生,該方法用來向對端發送消息。 |
開發步驟
Native側開發步驟
? 1. 添加依賴
SDK依賴:
#ipc場景 external_deps = [ "ipc:ipc_single", ] #rpc場景 external_deps = [ "ipc:ipc_core", ]
此外, IPC/RPC依賴的refbase實現在公共基礎庫下,請增加對utils的依賴:
external_deps = [ "c_utils:utils", ]
2.定義IPC接口ITestAbility
SA接口繼承IPC基類接口IRemoteBroker,接口里定義描述符、業務函數和消息碼,其中業務函數在Proxy端和Stub端都需要實現。
#include "iremote_broker.h" //定義消息碼 const int TRANS_ID_PING_ABILITY = 5 const std::string DESCRIPTOR = "test.ITestAbility"; class ITestAbility : public IRemoteBroker { public: // DECLARE_INTERFACE_DESCRIPTOR是必需的,入參需使用std::u16string; DECLARE_INTERFACE_DESCRIPTOR(to_utf16(DESCRIPTOR)); virtual int TestPingAbility(const std::u16string &dummy) = 0; // 定義業務函數 };
3.定義和實現服務端TestAbilityStub
該類是和IPC框架相關的實現,需要繼承 IRemoteStub。Stub端作為接收請求的一端,需重寫OnRemoteRequest方法用于接收客戶端調用。
#include "iability_test.h"
#include "iremote_stub.h"
class TestAbilityStub : public IRemoteStub {
public:
virtual int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) override;
int TestPingAbility(const std::u16string &dummy) override;
};
int TestAbilityStub::OnRemoteRequest(uint32_t code,
MessageParcel &data, MessageParcel &reply, MessageOption &option)
{
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);
}
}
? 4. 定義服務端業務函數具體實現類TestAbility
#include "iability_server_test.h"
class TestAbility : public TestAbilityStub {
public:
int TestPingAbility(const std::u16string &dummy);
}
int TestAbility::TestPingAbility(const std::u16string &dummy) {
return 0;
}
? 5. 定義和實現客戶端 TestAbilityProxy
該類是Proxy端實現,繼承IRemoteProxy,調用SendRequest接口向Stub端發送請求,對外暴露服務端提供的能力。
#include "iability_test.h"
#include "iremote_proxy.h"
#include "iremote_object.h"
class TestAbilityProxy : public IRemoteProxy {
public:
explicit TestAbilityProxy(const sptr &impl);
int TestPingAbility(const std::u16string &dummy) override;
private:
static inline BrokerDelegator delegator_; // 方便后續使用iface_cast宏
}
TestAbilityProxy::TestAbilityProxy(const sptr &impl)
: IRemoteProxy(impl)
{
}
int TestAbilityProxy::TestPingAbility(const std::u16string &dummy){
MessageOption option;
MessageParcel dataParcel, replyParcel;
dataParcel.WriteString16(dummy);
int error = Remote()->SendRequest(TRANS_ID_PING_ABILITY, dataParcel, replyParcel, option);
int result = (error == ERR_NONE) ? replyParcel.ReadInt32() : -1;
return result;
}
? 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 samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); sptr remoteObject = samgr->GetSystemAbility(saId); sptr testAbility = iface_cast(remoteObject); // 使用iface_cast宏轉換成具體類型 // 獲取其他設備注冊的SA的proxy sptr samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); // networkId是組網場景下對應設備的標識符,可以通過GetLocalNodeDeviceInfo獲取 sptr remoteObject = samgr->GetSystemAbility(saId, networkId); sptr proxy(new TestAbilityProxy(remoteObject)); // 直接構造具體Proxy
JS側開發步驟
? 1. 添加依賴
import rpc from "@ohos.rpc" import featureAbility from "@ohos.ability.featureAbility"
2.綁定Ability
首先,構造變量want,指定要綁定的Ability所在應用的包名、組件名,如果是跨設備的場景,還需要綁定目標設備NetworkId(組網場景下對應設備的標識符,可以使用deviceManager獲取目標設備的NetworkId);然后,構造變量connect,指定綁定成功、綁定失敗、斷開連接時的回調函數;最后,使用featureAbility提供的接口綁定Ability。
import rpc from "@ohos.rpc"
import featureAbility from "@ohos.ability.featureAbility"
let proxy = null
let connectId = null
// 單個設備綁定Ability
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 networkId = deviceList[0].networkId
let want = {
"bundleName": "ohos.rpc.test.server",
"abilityName": "ohos.rpc.test.service.ServiceAbility",
"networkId": networkId,
"flags": 256
}
connectId = featureAbility.connectAbility(want, connect)
}
// 第一個參數是本應用的包名,第二個參數是接收deviceManager的回調函數
deviceManager.createDeviceManager("ohos.rpc.test", deviceManagerCallback)
3.服務端處理客戶端請求
服務端被綁定的Ability在onConnect方法里返回繼承自rpc.RemoteObject的對象,該對象需要實現onRemoteMessageRequest方法,處理客戶端的請求。
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
}
}
4.客戶端處理服務端響應
客戶端在onConnect回調里接收到代理對象,調用sendRequestAsync方法發起請求,在期約(JavaScript期約:用于表示一個異步操作的最終完成或失敗及其結果值)或者回調函數里接收結果。
// 使用期約
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)
5.斷開連接
IPC通信結束后,使用featureAbility的接口斷開連接。
import rpc from "@ohos.rpc"
import featureAbility from "@ohos.ability.featureAbility"
function disconnectCallback() {
console.info("disconnect ability done")
}
featureAbility.disconnectAbility(connectId, disconnectCallback)
審核編輯 黃宇
-
RPC
+關注
關注
0文章
114瀏覽量
12268 -
IPC
+關注
關注
3文章
378瀏覽量
54942 -
鴻蒙
+關注
關注
60文章
2970瀏覽量
45994 -
HarmonyOS
+關注
關注
80文章
2155瀏覽量
36112
發布評論請先 登錄
Linux進程間通信(IPC)全解析:從管道到?Socket,一篇講透
鴻蒙OS跨進程IPC與RPC通信
【Linux學習雜談】之進程通信
HarmonyOS教程—基于分布式能力和IDL跨進程通信,實現視頻跨設備播放、控制
【中秋國慶不斷更】HarmonyOS跨進程通信—IPC與RPC通信開發指導
linux操作系統下的進程通信
Linux進程間的五種通信方式介紹 3
Linux進程間的五種通信方式介紹 4
Binder?驅動深度解析:Android IPC?的核心底層實現
HarmonyOS跨進程通信—IPC與RPC通信開發
評論