一、IPC與RPC通信概述
基本概念
IPC(Inter-Process Communication)與RPC(Remote Procedure Call)用于實(shí)現(xiàn)跨進(jìn)程通信,不同的是前者使用Binder驅(qū)動(dòng),用于設(shè)備內(nèi)的跨進(jìn)程通信,后者使用軟總線驅(qū)動(dòng),用于跨設(shè)備跨進(jìn)程通信。需要跨進(jìn)程通信的原因是因?yàn)槊總€(gè)進(jìn)程都有自己獨(dú)立的資源和內(nèi)存空間,其他進(jìn)程不能隨意訪問(wèn)不同進(jìn)程的內(nèi)存和資源,IPC/RPC便是為了突破這一點(diǎn)。IPC和RPC通常采用客戶(hù)端-服務(wù)器(Client-Server)模型,在使用時(shí),請(qǐng)求服務(wù)的(Client)一端進(jìn)程可獲取提供服務(wù)(Server)一端所在進(jìn)程的代理(Proxy),并通過(guò)此代理讀寫(xiě)數(shù)據(jù)來(lái)實(shí)現(xiàn)進(jìn)程間的數(shù)據(jù)通信,更具體的講,首先請(qǐng)求服務(wù)的(Client)一端會(huì)建立一個(gè)服務(wù)提供端(Server)的代理對(duì)象,這個(gè)代理對(duì)象具備和服務(wù)提供端(Server)一樣的功能,若想訪問(wèn)服務(wù)提供端(Server)中的某一個(gè)方法,只需訪問(wèn)代理對(duì)象中對(duì)應(yīng)的方法即可,代理對(duì)象會(huì)將請(qǐng)求發(fā)送給服務(wù)提供端(Server);然后服務(wù)提供端(Server)處理接受到的請(qǐng)求,處理完之后通過(guò)驅(qū)動(dòng)返回處理結(jié)果給代理對(duì)象;最后代理對(duì)象將請(qǐng)求結(jié)果進(jìn)一步返回給請(qǐng)求服務(wù)端(Client)。通常,Server會(huì)先注冊(cè)系統(tǒng)能力(System Ability)到系統(tǒng)能力管理者(System Ability Manager,縮寫(xiě)SAMgr)中,SAMgr負(fù)責(zé)管理這些SA并向Client提供相關(guān)的接口。Client要和某個(gè)具體的SA通信,必須先從SAMgr中獲取該SA的代理,然后使用代理和SA通信。下文直接使用Proxy表示服務(wù)請(qǐng)求方,Stub表示服務(wù)提供方。
約束與限制
? ● 單個(gè)設(shè)備上跨進(jìn)程通信時(shí),傳輸?shù)臄?shù)據(jù)量最大約為1MB,過(guò)大的數(shù)據(jù)量請(qǐng)使用匿名共享內(nèi)存
? ● 不支持在RPC中訂閱匿名Stub對(duì)象(沒(méi)有向SAMgr注冊(cè)Stub對(duì)象)的死亡通知。
? ● 不支持把跨設(shè)備的Proxy對(duì)象傳遞回該P(yáng)roxy對(duì)象所指向的Stub對(duì)象所在的設(shè)備,即指向遠(yuǎn)端設(shè)備Stub的Proxy對(duì)象不能在本設(shè)備內(nèi)進(jìn)行二次跨進(jìn)程傳遞。
使用建議
首先,需要編寫(xiě)接口類(lèi),接口類(lèi)中必須定義消息碼,供通信雙方標(biāo)識(shí)操作,可以有未實(shí)現(xiàn)的的方法,因?yàn)橥ㄐ烹p方均需繼承該接口類(lèi)且雙方不能是抽象類(lèi),所以此時(shí)定義的未實(shí)現(xiàn)的方法必須在雙方繼承時(shí)給出實(shí)現(xiàn),這保證了繼承雙方不是抽象類(lèi)。然后,需要編寫(xiě)Stub端相關(guān)類(lèi)及其接口,并且實(shí)現(xiàn)AsObject方法及OnRemoteRequest方法。同時(shí),也需要編寫(xiě)Proxy端,實(shí)現(xiàn)接口類(lèi)中的方法和AsObject方法,也可以封裝一些額外的方法用于調(diào)用SendRequest向?qū)Χ税l(fā)送數(shù)據(jù)。以上三者都具備后,便可以向SAMgr注冊(cè)SA了,此時(shí)的注冊(cè)應(yīng)該在Stub所在進(jìn)程完成。最后,在需要的地方從SAMgr中獲取Proxy,便可通過(guò)Proxy實(shí)現(xiàn)與Stub的跨進(jìn)程通信了。
相關(guān)步驟:
? ● 實(shí)現(xiàn)接口類(lèi):需繼承IRemoteBroker,需定義消息碼,可聲明不在此類(lèi)實(shí)現(xiàn)的方法。
? ● 實(shí)現(xiàn)服務(wù)提供端(Stub):需繼承IRemoteStub或者RemoteObject,需重寫(xiě)AsObject方法及OnRemoteRequest方法。
? ● 實(shí)現(xiàn)服務(wù)請(qǐng)求端(Proxy):需繼承IRemoteProxy或RemoteProxy,需重寫(xiě)AsObject方法,封裝所需方法調(diào)用SendRequest。
? ● 注冊(cè)SA:申請(qǐng)SA的唯一ID,向SAMgr注冊(cè)SA。
? ● 獲取SA:通過(guò)SA的ID和設(shè)備ID獲取Proxy,使用Proxy與遠(yuǎn)端通信
二、 IPC與RPC通信開(kāi)發(fā)指導(dǎo)
場(chǎng)景介紹
IPC/RPC的主要工作是讓運(yùn)行在不同進(jìn)程的Proxy和Stub互相通信,包括Proxy和Stub運(yùn)行在不同設(shè)備的情況。
接口說(shuō)明
表1 Native側(cè)IPC接口
類(lèi)/接口 | 方法 | 功能說(shuō)明 |
---|---|---|
IRemoteBroker | sptr AsObject() | 返回通信對(duì)象。Stub端返回RemoteObject對(duì)象本身,Proxy端返回代理對(duì)象。 |
IRemoteStub | virtual int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) | 請(qǐng)求處理方法,派生類(lèi)需要重寫(xiě)該方法用來(lái)處理Proxy的請(qǐng)求并返回結(jié)果。 |
IRemoteProxy | Remote()->SendRequest(code, data, reply, option) | 消息發(fā)送方法,業(yè)務(wù)的Proxy類(lèi)需要從IRemoteProxy類(lèi)派生,該方法用來(lái)向?qū)Χ税l(fā)送消息。 |
開(kāi)發(fā)步驟
Native側(cè)開(kāi)發(fā)步驟
? 1. 添加依賴(lài)
SDK依賴(lài):
#ipc場(chǎng)景 external_deps = [ "ipc:ipc_single", ] #rpc場(chǎng)景 external_deps = [ "ipc:ipc_core", ]
此外, IPC/RPC依賴(lài)的refbase實(shí)現(xiàn)在公共基礎(chǔ)庫(kù)下,請(qǐng)?jiān)黾訉?duì)utils的依賴(lài):
external_deps = [ "c_utils:utils", ]
2.定義IPC接口ITestAbility
SA接口繼承IPC基類(lèi)接口IRemoteBroker,接口里定義描述符、業(yè)務(wù)函數(shù)和消息碼,其中業(yè)務(wù)函數(shù)在Proxy端和Stub端都需要實(shí)現(xiàn)。
#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是必需的,入?yún)⑿枋褂胹td::u16string; DECLARE_INTERFACE_DESCRIPTOR(to_utf16(DESCRIPTOR)); virtual int TestPingAbility(const std::u16string &dummy) = 0; // 定義業(yè)務(wù)函數(shù) };
3.定義和實(shí)現(xiàn)服務(wù)端TestAbilityStub
該類(lèi)是和IPC框架相關(guān)的實(shí)現(xiàn),需要繼承 IRemoteStub。Stub端作為接收請(qǐng)求的一端,需重寫(xiě)OnRemoteRequest方法用于接收客戶(hù)端調(diào)用。
#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. 定義服務(wù)端業(yè)務(wù)函數(shù)具體實(shí)現(xiàn)類(lèi)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. 定義和實(shí)現(xiàn)客戶(hù)端 TestAbilityProxy
該類(lèi)是Proxy端實(shí)現(xiàn),繼承IRemoteProxy,調(diào)用SendRequest接口向Stub端發(fā)送請(qǐng)求,對(duì)外暴露服務(wù)端提供的能力。
#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_; // 方便后續(xù)使用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注冊(cè)與啟動(dòng)
SA需要將自己的TestAbilityStub實(shí)例通過(guò)AddSystemAbility接口注冊(cè)到SystemAbilityManager,設(shè)備內(nèi)與分布式的注冊(cè)參數(shù)不同。
// 注冊(cè)到本設(shè)備內(nèi) auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); samgr->AddSystemAbility(saId, new TestAbility()); // 在組網(wǎng)場(chǎng)景下,會(huì)被同步到其他設(shè)備上 auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); ISystemAbilityManager::SAExtraProp saExtra; saExtra.isDistributed = true; // 設(shè)置為分布式SA int result = samgr->AddSystemAbility(saId, new TestAbility(), saExtra);
? 7. SA獲取與調(diào)用
通過(guò)SystemAbilityManager的GetSystemAbility方法可獲取到對(duì)應(yīng)SA的代理IRemoteObject,然后構(gòu)造TestAbilityProxy即可。
// 獲取本設(shè)備內(nèi)注冊(cè)的SA的proxy sptr samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); sptr remoteObject = samgr->GetSystemAbility(saId); sptr testAbility = iface_cast(remoteObject); // 使用iface_cast宏轉(zhuǎn)換成具體類(lèi)型 // 獲取其他設(shè)備注冊(cè)的SA的proxy sptr samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); // networkId是組網(wǎng)場(chǎng)景下對(duì)應(yīng)設(shè)備的標(biāo)識(shí)符,可以通過(guò)GetLocalNodeDeviceInfo獲取 sptr remoteObject = samgr->GetSystemAbility(saId, networkId); sptr proxy(new TestAbilityProxy(remoteObject)); // 直接構(gòu)造具體Proxy
JS側(cè)開(kāi)發(fā)步驟
? 1. 添加依賴(lài)
import rpc from "@ohos.rpc" import featureAbility from "@ohos.ability.featureAbility"
2.綁定Ability
首先,構(gòu)造變量want,指定要綁定的Ability所在應(yīng)用的包名、組件名,如果是跨設(shè)備的場(chǎng)景,還需要綁定目標(biāo)設(shè)備N(xiāo)etworkId(組網(wǎng)場(chǎng)景下對(duì)應(yīng)設(shè)備的標(biāo)識(shí)符,可以使用deviceManager獲取目標(biāo)設(shè)備的NetworkId);然后,構(gòu)造變量connect,指定綁定成功、綁定失敗、斷開(kāi)連接時(shí)的回調(diào)函數(shù);最后,使用featureAbility提供的接口綁定Ability。
import rpc from "@ohos.rpc" import featureAbility from "@ohos.ability.featureAbility" let proxy = null let connectId = null // 單個(gè)設(shè)備綁定Ability let want = { // 包名和組件名寫(xiě)實(shí)際的值 "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) // 如果是跨設(shè)備綁定,可以使用deviceManager獲取目標(biāo)設(shè)備N(xiāo)etworkId 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) } // 第一個(gè)參數(shù)是本應(yīng)用的包名,第二個(gè)參數(shù)是接收deviceManager的回調(diào)函數(shù) deviceManager.createDeviceManager("ohos.rpc.test", deviceManagerCallback)
3.服務(wù)端處理客戶(hù)端請(qǐng)求
服務(wù)端被綁定的Ability在onConnect方法里返回繼承自rpc.RemoteObject的對(duì)象,該對(duì)象需要實(shí)現(xiàn)onRemoteMessageRequest方法,處理客戶(hù)端的請(qǐng)求。
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) { // 根據(jù)code處理客戶(hù)端的請(qǐng)求 return true } }
4.客戶(hù)端處理服務(wù)端響應(yīng)
客戶(hù)端在onConnect回調(diào)里接收到代理對(duì)象,調(diào)用sendRequestAsync方法發(fā)起請(qǐng)求,在期約(JavaScript期約:用于表示一個(gè)異步操作的最終完成或失敗及其結(jié)果值)或者回調(diào)函數(shù)里接收結(jié)果。
// 使用期約 let option = new rpc.MessageOption() let data = rpc.MessageParcel.create() let reply = rpc.MessageParcel.create() // 往data里寫(xiě)入?yún)?shù) proxy.sendRequestAsync(1, data, reply, option) .then(function(result) { if (result.errCode != 0) { console.error("send request failed, errCode: " + result.errCode) return } // 從result.reply里讀取結(jié)果 }) .catch(function(e) { console.error("send request got exception: " + e) } .finally(() => { data.reclaim() reply.reclaim() }) // 使用回調(diào)函數(shù) function sendRequestCallback(result) { try { if (result.errCode != 0) { console.error("send request failed, errCode: " + result.errCode) return } // 從result.reply里讀取結(jié)果 } finally { result.data.reclaim() result.reply.reclaim() } } let option = new rpc.MessageOption() let data = rpc.MessageParcel.create() let reply = rpc.MessageParcel.create() // 往data里寫(xiě)入?yún)?shù) proxy.sendRequest(1, data, reply, option, sendRequestCallback)
5.斷開(kāi)連接
IPC通信結(jié)束后,使用featureAbility的接口斷開(kāi)連接。
import rpc from "@ohos.rpc" import featureAbility from "@ohos.ability.featureAbility" function disconnectCallback() { console.info("disconnect ability done") } featureAbility.disconnectAbility(connectId, disconnectCallback)
審核編輯 黃宇
-
RPC
+關(guān)注
關(guān)注
0文章
111瀏覽量
11541 -
IPC
+關(guān)注
關(guān)注
3文章
352瀏覽量
51976 -
鴻蒙
+關(guān)注
關(guān)注
57文章
2377瀏覽量
42934 -
HarmonyOS
+關(guān)注
關(guān)注
79文章
1980瀏覽量
30322
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論