HarmonyOS概述
系統定義
系統定位
HarmonyOS [1] 目前的穩定版本為2.0,這是一款“面向未來”、面向全場景(移動辦公、運動健康、社交通信、媒體娛樂等)的分布式操作系統。在傳統的單設備系統能力的基礎上,HarmonyOS提出了基於同一套系統能力、適配多種終端形態的分布式理念,能夠支持手機、平板、智能穿戴、智慧屏、車機等多種終端設備。
- 對消費者而言,HarmonyOS能夠將生活場景中的各類終端進行能力整合,可以實現不同的終端設備之間的快速連接、能力互助、資源共享,匹配合適的設備、提供流暢的全場景體驗。
- 對應用開發者而言,HarmonyOS采用了多種分布式技術,使得應用程序的開發實現與不同終端設備的形態差異無關。這能夠讓開發者聚焦上層業務邏輯,更加便捷、高效地開發應用。
- 對設備開發者而言,HarmonyOS采用了組件化的設計方案,可以根據設備的資源能力和業務特征進行靈活裁剪,滿足不同形態的終端設備對於操作系統的要求。
HarmonyOS提供了支持多種開發語言的API,供開發者進行應用開發。支持的開發語言包括Java、XML(Extensible Markup Language)、C/C++ 、 JS(JavaScript)、CSS(Cascading Style Sheets)和HML(HarmonyOS Markup Language)。
系統架構
HarmonyOS整體遵從分層設計,從下向上依次為:內核層、系統服務層、框架層和應用層。系統功能按照“系統 > 子系統 > 功能/模塊”逐級展開,在多設備部署場景下,支持根據實際需求裁剪某些非必要的子系統或功能/模塊。HarmonyOS技術架構如下所示。
1. 內核層
- 內核子系統:
HarmonyOS采用多內核設計,支持針對不同資源受限設備選用適合的OS內核。內核抽象層(KAL,Kernel Abstract Layer)通過屏蔽多內核差異,對上層提供基礎的內核能力,包括進程/線程管理、內存管理、文件系統、網絡管理和外設管理等。 - 驅動子系統:
硬件驅動框架(HDF)是HarmonyOS硬件生態開放的基礎,提供統一外設訪問能力和驅動開發、管理框架。
2. 系統服務層
系統服務層是HarmonyOS的核心能力集合,通過框架層對應用程序提供服務。該層包含以下幾個部分:
- 系統基本能力子系統集:
為分布式應用在HarmonyOS多設備上的運行、調度、遷移等操作提供了基礎能力,由分布式軟總線、分布式數據管理、分布式任務調度、方舟多語言運行時、公共基礎庫、多模輸入、圖形、安全、AI等子系統組成。其中,方舟運行時提供了C/C++/JS多語言運行時和基礎的系統類庫,也為使用方舟編譯器靜態化的Java程序(即應用程序或框架層中使用Java語言開發的部分)提供運行時。 - 基礎軟件服務子系統集:
為HarmonyOS提供公共的、通用的軟件服務,由事件通知、電話、多媒體、DFX(Design For X) 、MSDP&DV等子系統組成。 - 增強軟件服務子系統集:
為HarmonyOS提供針對不同設備的、差異化的能力增強型軟件服務,由智慧屏專有業務、穿戴專有業務、IoT專有業務等子系統組成。 - 硬件服務子系統集:
為HarmonyOS提供硬件服務,由位置服務、生物特征識別、穿戴專有硬件服務、IoT專有硬件服務等子系統組成。
根據不同設備形態的部署環境,基礎軟件服務子系統集、增強軟件服務子系統集、硬件服務子系統集內部可以按子系統粒度裁剪,每個子系統內部又可以按功能粒度裁剪。
3. 框架層
框架層為HarmonyOS應用開發提供了Java/C/C++/JS等多語言的用戶程序框架和Ability框架,兩種UI框架(包括適用於Java語言的Java UI框架、適用於JS語言的JS UI框架),以及各種軟硬件服務對外開放的多語言框架API。根據系統的組件化裁剪程度,HarmonyOS設備支持的API也會有所不同。
4. 應用層
應用層包括系統應用和第三方非系統應用。HarmonyOS的應用由一個或多個FA(Feature Ability)或PA(Particle Ability)組成。其中,FA有UI界面,提供與用戶交互的能力;而PA無UI界面,提供后台運行任務的能力以及統一的數據訪問抽象。FA在進行用戶交互時所需的后台數據訪問也需要由對應的PA提供支撐。基於FA/PA開發的應用,能夠實現特定的業務功能,支持跨設備調度與分發,為用戶提供一致、高效的應用體驗。
技術特性
硬件互助,資源共享
多種設備之間能夠實現硬件互助、資源共享,依賴的關鍵技術包括分布式軟總線、分布式設備虛擬化、分布式數據管理、分布式任務調度等。
分布式軟總線
分布式軟總線是手機、平板、智能穿戴、智慧屏、車機等分布式設備的通信基座,為設備之間的互聯互通提供了統一的分布式通信能力,為設備之間的無感發現和零等待傳輸創造了條件。開發者只需聚焦於業務邏輯的實現,無需關注組網方式與底層協議。分布式軟總線示意圖如下。
典型應用場景舉例:
- 智能家居場景:在制作粉蒸肉時,手機可以通過碰一碰和烤箱連接,並將自動設置粉蒸肉的制作參數,控制烤箱來制作菜餚。與此類似,料理機、油煙機、空氣凈化器、空調、燈、窗簾等都可以在手機端顯示並通過手機控制。設備之間即連即用,無需繁瑣的配置。
- 多屏聯動課堂:老師通過智慧屏授課,與學生開展互動,營造課堂氛圍;學生通過手機完成課程學習和隨堂問答。統一、全連接的邏輯網絡確保了傳輸通道的高帶寬、低時延、高可靠。
分布式設備虛擬化
分布式設備虛擬化平台可以實現不同設備的資源融合、設備管理、數據處理,多種設備共同形成一個超級虛擬終端。針對不同類型的任務,為用戶匹配並選擇能力合適的執行硬件,讓業務連續地在不同設備間流轉,充分發揮不同設備的能力優勢,如顯示能力、攝像能力、音頻能力、交互能力以及傳感器能力等。分布式設備虛擬化示意圖如下。
典型應用場景舉例:
- 視頻通話場景:在做家務時接聽視頻電話,可以將手機與智慧屏連接,並將智慧屏的屏幕、攝像頭與音箱虛擬化為本地資源,替代手機自身的屏幕、攝像頭、聽筒與揚聲器,實現一邊做家務、一邊通過智慧屏和音箱來視頻通話。
- 游戲場景:在智慧屏上玩游戲時,可以將手機虛擬化為遙控器,借助手機的重力傳感器、加速度傳感器、觸控能力,為玩家提供更便捷、更流暢的游戲體驗。
分布式數據管理
分布式數據管理基於分布式軟總線的能力,實現應用程序數據和用戶數據的分布式管理。用戶數據不再與單一物理設備綁定,業務邏輯與數據存儲分離,跨設備的數據處理如同本地數據處理一樣方便快捷,讓開發者能夠輕松實現全場景、多設備下的數據存儲、共享和訪問,為打造一致、流暢的用戶體驗創造了基礎條件。分布式數據管理示意圖如下。
典型應用場景舉例:
- 協同辦公場景:將手機上的文檔投屏到智慧屏,在智慧屏上對文檔執行翻頁、縮放、塗鴉等操作,文檔的最新狀態可以在手機上同步顯示。
- 家庭出游場景:一家人出游時,媽媽用手機拍了很多照片。通過家庭照片共享,爸爸可以在自己的手機上瀏覽、收藏和保存這些照片,家中的爺爺奶奶也可以通過智慧屏瀏覽這些照片。
分布式任務調度
分布式任務調度基於分布式軟總線、分布式數據管理、分布式Profile等技術特性,構建統一的分布式服務管理(發現、同步、注冊、調用)機制,支持對跨設備的應用進行遠程啟動、遠程調用、遠程連接以及遷移等操作,能夠根據不同設備的能力、位置、業務運行狀態、資源使用情況,以及用戶的習慣和意圖,選擇合適的設備運行分布式任務。
下圖以應用遷移為例,簡要地展示了分布式任務調度能力。
典型應用場景舉例:
- 導航場景:如果用戶駕車出行,上車前,在手機上規划好導航路線;上車后,導航自動遷移到車機和車載音箱;下車后,導航自動遷移回手機。如果用戶騎車出行,在手機上規划好導航路線,騎行時手表可以接續導航。
- 外賣場景:在手機上點外賣后,可以將訂單信息遷移到手表上,隨時查看外賣的配送狀態。
一次開發,多端部署
HarmonyOS提供了用戶程序框架、Ability框架以及UI框架,支持應用開發過程中多終端的業務邏輯和界面邏輯進行復用,能夠實現應用的一次開發、多端部署,提升了跨設備應用的開發效率。一次開發、多端部署示意圖如下。
其中,UI框架支持Java和JS兩種開發語言,並提供了豐富的多態控件,可以在手機、平板、智能穿戴、智慧屏、車機上顯示不同的UI效果。采用業界主流設計方式,提供多種響應式布局方案,支持柵格化布局,滿足不同屏幕的界面適配能力。
統一OS,彈性部署
HarmonyOS通過組件化和小型化等設計方法,支持多種終端設備按需彈性部署,能夠適配不同類別的硬件資源和功能需求。支撐通過編譯鏈關系去自動生成組件化的依賴關系,形成組件樹依賴圖,支撐產品系統的便捷開發,降低硬件設備的開發門檻。
- 支持各組件的選擇(組件可有可無): 根據硬件的形態和需求,可以選擇所需的組件。
- 支持組件內功能集的配置(組件可大可小): 根據硬件的資源情況和功能需求,可以選擇配置組件中的功能集。例如,選擇配置圖形框架組件中的部分控件。
- 支持組件間依賴的關聯(平台可大可小): 根據編譯鏈關系,可以自動生成組件化的依賴關系。例如,選擇圖形框架組件,將會自動選擇依賴的圖形引擎組件等。
系統安全
在搭載HarmonyOS的分布式終端上,可以保證“正確的人,通過正確的設備,正確地使用數據”。
- 通過“分布式多端協同身份認證”來保證“正確的人”。 在分布式終端場景下,“正確的人”指通過身份認證的數據訪問者和業務操作者。“正確的人”是確保用戶數據不被非法訪問、用戶隱私不泄露的前提條件。
- 通過“在分布式終端上構築可信運行環境”來保證“正確的設備”。 在分布式終端場景下,只有保證用戶使用的設備是安全可靠的,才能保證用戶數據在虛擬終端上得到有效保護,避免用戶隱私泄露。
- 通過“分布式數據在跨終端流動的過程中,對數據進行分類分級管理”來保證“正確地使用數據”。 在分布式終端場景下,需要確保用戶能夠正確地使用數據。HarmonyOS圍繞數據的生成、存儲、使用、傳輸以及銷毀過程進行全生命周期的保護,從而保證個人數據與隱私、以及系統的機密數據(如密鑰)不泄漏。
分布式軟總線模塊詳述
什么是總線
總線英文名叫Bus,就是公共汽車的意思,正如公共汽車作為運載人的一種載具一樣,總線是作為信號傳遞的統一通道。它是一個非常廣泛的概念,在傳統計算機硬件體系中應用的非常廣泛。
總線是一種內部結構,它是cpu、內存、輸入、輸出設備傳遞信息的公用通道,主機的各個部件通過總線相連接,外部設備通過相應的接口電路再與總線相連接,從而形成了計算機硬件系統。
在計算機系統中,各個部件之間傳送信息的公共通路叫總線,微型計算機是以總線結構來連接各個功能部件的。按照計算機所傳輸的信息種類,計算機的總線可以划分為數據總線、地址總線和控制總線,分別用來傳輸數據、數據地址和控制信號。
傳統總線的典型特征:
- 即插即用
- 高帶寬
- 低時延
- 高可靠
- 標准性
什么是分布式軟總線
分布式軟總線[2]技術是基於華為多年的通信技術積累,參考計算機硬件總線,在1+8+N設備間搭建一條“無形”的總線,具備自發現、自組網、高帶寬低時延的特點。
1+8+N:
1指的是手機
8代表車機、音箱、耳機、手表/手環、平板、大屏、PC、AR/VR
N泛指其他IOT設備
分布式軟總線其實指的是一種多類型網絡通信協議,它可以把所有鴻蒙設備連在一起,通過該協議進行通信。這里華為把它抽象成一根類似物理上的總線,就好像I2C總線,在SDA、SCL兩根線上可以掛載許多設備,設備可以通過I2C總線進行互相通信。
傳統開發跨端應用,開發者們常常面臨以下挑戰:
- 跨端操作需要每次重復建立連接
- 近場通信方式(藍牙,WiFi,UWB等)必須感知才能實現連接
- 無線環境必須建立標准的用於服務器的協議棧
- 不同物理層無法實現統一的開發體驗
針對這些開發者的開發之痛,分布式軟總線的技術帶來全新解決思路。它采用極簡通信協議技術,包括發現&連接、組網(多跳自組網、多協議混合組網)、傳輸(極簡傳輸協議:多元化協議與算法、智能感知與決策),以及開發者如何基於分布式軟總線進行“三步走”極簡開發。
分布式軟總線為設備之間的互聯互通提供了統一的分布式通信能力,為設備之間的無感發現和零等待傳輸創造了條件。開發者只需聚焦於業務邏輯的實現,無需關注組網方式與底層協議。
全場景設備間可以基於軟總線完成設備虛擬化、跨設備服務調用、多屏協同、文件分享等分布式業務。鴻蒙分布式軟總線致力於實現近場設備間統一的分布式通信能力,提供不區分鏈路的設備發現和傳輸接口,具備快速發現並連接設備,高效分發任務和傳輸數據。作為多終端設備的統一基座,是鴻蒙架構中的底層技術,是鴻蒙的大動脈,其總的目標是實現設備間無感發現,零等待傳輸。對開發者而言,無需關注組網方式與底層協議。
分布式軟總線的典型特征:
- 自動發現/即連即用
- 高帶寬
- 低時延
- 高可靠
- 開放/標准
分布式軟總線功能和原理
通過協議貨架和軟硬協同層屏蔽各種設備的協議差別,總線中樞模塊負責解析命令完成設備間發現和連接,通過任務和數據兩條總線實現設備間文件傳輸、消息傳輸等功能。
分布式總線的總體目標是實現設備間無感發現,零等待傳輸。實現這個目標需要解決三個問題:
- 設備間如何發現和連接?
- 多設備互聯后如何組網?
- 多設備多協議間如何實現傳輸?
設備間自發現&連接
傳統的設備發現是手動的,需要人干預,以生活中常見的一個例子講解:
比如手機上有很多照片需要傳到個人PC上,我們可以采用藍牙傳輸,首先要打開手機和PC的藍牙發現功能,手機或者PC點擊搜索設備,然后互相配對授權即可連接上,成功連上后就可以肆無忌憚的發送照片啦。在分享照片這個場景中有很多人為的動作:開啟藍牙發現功能、搜索設備、配對授權,這確實有點麻煩,耗費了很多時間,可能會降低分享的意願。
分布式軟總線提出自動發現設備,實現用戶零等待的自發現體驗,附近同賬號的設備自動發現無需等待,自動安全連接。
IoT設備分為發現端和被發現端。發現端一般為請求使用服務的設備或稱為主控設備,常指智慧屏設備(如手機、平板等)。被發現端為發布服務的設備,指輕量設備(如AI音箱、智能家居、智能穿戴等設備)。目前軟總線的設備互聯,需保證發現端和被發現端處於同一個局域網內。
發現端設備,發起discover請求后,使用coap協議在局域網內發送廣播。報文如下:
被發現端設備使用PublishService
接口發布服務,接收端收到廣播后,發送coap協議單播給發現端。報文格式如下:
發現端設備收到報文會更新設備信息。
發現的流程圖如下:
多設備互聯、組網
基於網絡互聯、交互的系統,開發者往往需要適配不同網絡協議和標准規范。而在鴻蒙系統設定的分布式開發模式中,無需關心網絡協議的差異及組網方式,業務開發與設備組網解耦,僅需監聽設備上下線,開發成本大大降低。
分布式軟總線提出了異構網絡組網,自動構建一個邏輯全連接網絡,以解決設備間不同協議交互的問題。設備上線后會向網絡層注冊,同時網絡層會與設備建立通道連接,實時檢測設備的變換。網絡層負責管理設備的上線、下線變換,設備間可以監聽自己感興趣的設備,設備上線后可以立即與其建立連接,實現零等待體驗。
設備上線后會向網絡層注冊,同時網絡層會與設備建立通道連接,實時檢測設備的變換。網絡層負責管理設備的上線下線變換,設備間可以監聽自己感興趣的設備,設備上線后可以立即與其建立連接,實現零等待體驗。
軟總線可以自動構建一個邏輯全連接網絡,用戶或者業務開發者無需關心組網方式與物理協議。對於軟件開發者來說軟總線異構組網可以大大降低其開發成本。
在傳統開發模式中開發者需要適配不同網絡協議和標准規范。
在HarmonyOS分布式開發模式中開發不再需要關心網絡協議差異,業務開發與設備組網解耦,業務僅需監聽設備上下線,開發成本大大降低。
多設備間數據傳輸
傳統協議的傳輸速率差異非常大,時延也難以得到保證。
軟總線傳輸要實現的目標:
- 高帶寬(High Speed)
- 低時延(Low Latency)
- 高可靠(High Reliability)
軟總線要實現的這三大目標的尖刀武器是:極簡協議。
將中間的四層協議棧精簡為一層提升有效載荷,有效傳輸帶寬提升20%。
極簡協議在傳統網絡協議的基礎上進行增強:
- 流式傳輸:基於UDP實現數據的保序和可靠傳輸;
- 雙輪驅動:顛覆傳統TCP每包確認機制;
- 不懼網損:摒棄傳統滑動窗口機制,丟包快速恢復,避免阻塞;
- 不懼抖動:智能感知網絡變化,自適應流量控制和擁塞控制;
分布式軟總線源碼分析
源碼目錄結構
HarmonyOS[3]代碼已在gitee開源,其中分布式軟中線代碼倉庫地址如下:
communication_interfaces_kits_softbuskit_lite
communication_services_softbus_lite
顧名思義,分別對應它的接口和實現;而communication_services_softbus_lite源碼結構中,又分為authmanager
、discovery
、trans_service
和為兼容系統差別而生的os_adapter
四大目錄。
authmanager
提供設備認證機制和設備知識庫管理,當發現有請求時,調用ProcessDataEvent函數,收包,檢驗包頭,根據數據包的類型確定不同的處理方式。類型主要包括以下三種:
- MODULE_AUTH_SDK 加密數據類型
- MODULE_TRUST_ENGINE 可信類型,直接進行數據傳輸
- MODULE_CONNECTION 進行ip及設備認證
authmanager源碼結構及功能概述如下:
-
auth_conn.c
提供發送、接收、認證、獲取秘鑰功能; -
auth_interface.c
管理各個會話節點、各個鏈接節點、各個秘鑰節點,提供包括增刪改查等功能; -
msg_get_deviceid.c
提供以cJSON格式獲取各個設備的信息,包括設備id、鏈接信息、設備名、設備類型等; -
bus_manager.c
主要通過deviceIp創建兩個不同的listen,主要用來監聽系統上有哪些device及新的device節點的創建;其中有兩個回調函數OnConnectEvent和OnDataEvent,分別是用來處理設備節點的基本操作及節點數據的處理; -
wifi_auth_manager.c
主要實現了連接管理和數據接收功能。連接管理包括連接的建立、斷開及連接的查找。數據接收包括數據獲取、包頭及包長等的校驗,並且為了簡化包頭數據讀取,單獨實現了對一個int型和一個long型數據的接收函數。
discover
它是一種基於coap協議的設備發現機制,為什么選擇coap協議?
因為物聯網設備的ram,rom都通常非常小,運行TCP和HTTP是不可以接受的。而coap(Constrained Application Protocol、受限應用協議)是一種在物聯網世界的類web協議,顧名思義,可以使用在資源受限的物聯網設備上。
它支持可靠傳輸的輕量化協議。discover的設備發現功能就是用的這個特性。
discovery 代碼包含coap和discovery_service兩部分,coap部分是coap協議封裝實現,discovery_service 是基於coap協議設備的發現流程實現。
discovery的實現前提是確保發現端設備與接收端設備在同一個局域網內且能互相收到對方的報文。大致流程是:
- 發現端設備,使用coap協議在局域網內發送廣播;
- 接收端設備使用PublishService接口發布服務,接收端收到廣播后,發送coap協議單播給發現端;
- 發現端設備收到報文會更新設備信息。
trans_service
該目錄中的代碼提供身份驗證和傳輸通道。它主要封裝了socket、cJSON、線程鎖接口,實現了用戶的創建、監聽、會話管理,以及設備、指令、數據等信息的獲取,最終提供加密和解密傳輸兩種傳輸通道。
trans_service源碼結構及功能概述如下:
-
auth_conn_manager.c
用戶創建,監聽,連接等服務管理; -
tcp_session_manager.c
會話管理; -
trans_lock.c
互斥鎖初始化以及互斥鎖資源獲取與釋放; -
aes_gcm.c
提供加密傳輸和解密傳輸接口; -
messages.c
用於獲取以cJSON格式管理的設備(包括設備名、設備類型、設備ID等)、指令、數據、會話(包括用戶端口、會話端口等)等信息; -
tcp_socket.c
端口號管理以及數據傳輸管理。
trans_service模塊
trans_service模塊依賴於系統OS提供的網絡socket服務,向認證模塊提供認證通道管理和認證數據的收發;向業務模塊提供session管理和基於session的數據收發功能,並且通過GCM模塊的加密功能提供收發報文的加解密保護。
- 初始化的時機
在分布式軟總線的設計中,trans_service模塊是在authmanager模塊中被初始化的,而authmanager模塊又被discovery模塊初始化,因此設備在向外發布本設備信息的過程中,即完成了這三個相互關聯模塊的初始化動作。
authmanager模塊中存在StartBus()函數,其中,StartListener()函數負責為認證模塊提供通道完成初始化,StartSession()函數負責初始化業務的session管理:
int StartBus(void)
{
if (g_busStartFlag == 1) {
return 0;
}
DeviceInfo *info = GetCommonDeviceInfo();
if (info == NULL) {
return ERROR_FAIL;
}
g_baseLister.onConnectEvent = OnConnectEvent;
g_baseLister.onDataEvent = OnDataEvent;
int authPort = StartListener(&g_baseLister, info->deviceIp);
if (authPort < 0) {
SOFTBUS_PRINT("[AUTH] StartBus StartListener fail\n");
return ERROR_FAIL;
}
info->devicePort = authPort;
int sessionPort = StartSession(info->deviceIp);
if (sessionPort < 0) {
SOFTBUS_PRINT("[AUTH] StartBus StartSession fail\n");
StopListener();
return ERROR_FAIL;
}
AuthMngInit(authPort, sessionPort);
g_busStartFlag = 1;
SOFTBUS_PRINT("[AUTH] StartBus ok\n");
return 0;
}
認證通信與業務session的實現原理也類似
- 初始化入口 – StartListener
StartListener()函數的底層存在對應不同版本平台的適配函數,這印證了鴻蒙OS各部分解耦的模塊化設計思想,針對不同的硬件設備,組合成最適合該設備的OS。比如創建線程時采用了統一的static void WaitProcess(void)函數,而其內部封裝了不同底層API的適配代碼。
int StartListener(BaseListener *callback, const char *ip)
{
if (callback == NULL || ip == NULL) {
return -DBE_BAD_PARAM;
}
g_callback = callback;
int rc = InitListenFd(ip, SESSIONPORT);
if (rc != DBE_SUCCESS) {
return -DBE_BAD_PARAM;
}
unsigned int ret;
TSK_INIT_PARAM_S serverTask;
serverTask.pfnTaskEntry = (TSK_ENTRY_FUNC)WaitProcess;
serverTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
serverTask.pcName = "trans_auth_task";
serverTask.usTaskPrio = LOSCFG_BASE_CORE_TSK_DEFAULT_PRIO;
serverTask.uwResved = LOS_TASK_STATUS_DETACHED;
ret = LOS_TaskCreate(&g_uwTskLoID, &serverTask);
if (ret != 0) {
SOFTBUS_PRINT("[TRANS] StartListener task create fail\n");
return -1;
}
SOFTBUS_PRINT("[TRANS] StartListener ok\n");
return GetSockPort(g_listenFd);
}
StartListener()調用InitListenFd()函數完成監聽TCP socket的創建和監聽,其中IP地址和端口號由上層調用者指定。
static int InitListenFd(const char *ip, int port)
{
if (ip == NULL || g_listenFd != -1) {
return -DBE_BAD_PARAM;
}
if (strncmp(ip, "0.0.0.0", strlen(ip)) == 0) {
return -DBE_BAD_PARAM;
}
int rc = OpenTcpServer(ip, port);
if (rc < 0) {
SOFTBUS_PRINT("[TRANS] InitListenFd OpenTcpServer fail\n");
return rc;
}
g_listenFd = rc;
RefreshMaxFd(g_listenFd);
rc = listen(rc, DEFAULT_BACKLOG);
if (rc != 0) {
SOFTBUS_PRINT("[TRANS] InitListenFd listen fail\n");
StopListener();
return -DBE_LISTEN_FAIL;
}
return DBE_SUCCESS;
}
如上所述,AuthCreate()在不同平台上會有不同的實現,在LITEOS_A和Linux平台上, AuthCreate()會調用兼容POSIX的pthread_create()完成線程的創建,線程的入口函數為static void WaitProcess(void)。
signal(SIGPIPE, SIG_IGN);
ThreadAttr attr = {"auth", 0x800, 20, 0, 0};
register ThreadId threadId = (ThreadId)AuthCreate((Runnable)WaitProcess, &attr);
if (threadId == NULL) {
SOFTBUS_PRINT("[TRANS] StartListener AuthCreate fail\n");
return -1;
}
return GetSockPort(g_listenFd);
- 監聽新連接和數據 – WaitProcess
static void WaitProcess(void)
{
SOFTBUS_PRINT("[TRANS] WaitProcess begin\n");
fd_set readSet;
fd_set exceptfds;
while (1) {
FD_ZERO(&readSet);
FD_ZERO(&exceptfds);
FD_SET(g_listenFd, &readSet);
if (g_dataFd >= 0) {
FD_SET(g_dataFd, &readSet);
FD_SET(g_dataFd, &exceptfds);
}
int ret = select(g_maxFd + 1, &readSet, NULL, &exceptfds, NULL);
if (ret > 0) {
if (!ProcessAuthData(g_listenFd, &readSet)) {
SOFTBUS_PRINT("[TRANS] WaitProcess ProcessAuthData fail\n");
StopListener();
break;
}
} else if (ret < 0) {
if (errno == EINTR || (g_dataFd > 0 && FD_ISSET(g_dataFd, &exceptfds))) {
SOFTBUS_PRINT("[TRANS] errno == EINTR or g_dataFd is in exceptfds set.\n");
CloseAuthSessionFd(g_dataFd);
continue;
}
SOFTBUS_PRINT("[TRANS] WaitProcess select fail, stop listener\n");
StopListener();
break;
}
}
}
WaitProcess()使用忙等方式,調用select()來監聽listenFd和數據g_dataFd的信息,如果監聽到有數據可讀,則進入ProcessAuthData來處理。
如果發現g_dataFd有異常信息,則將其關閉。其中g_dataFd是由listenFd監聽到連接時創建的socket。
- 處理新連接和數據 - ProcessAuthData
static bool ProcessAuthData(int listenFd, const fd_set *readSet)
{
if (readSet == NULL || g_callback == NULL || g_callback->onConnectEvent == NULL ||
g_callback->onDataEvent == NULL) {
return false;
}
if (FD_ISSET(listenFd, readSet)) {
struct sockaddr_in addrClient = {0};
socklen_t addrLen = sizeof(addrClient);
g_dataFd = accept(listenFd, (struct sockaddr *)(&addrClient), &addrLen);
if (g_dataFd < 0) {
CloseAuthSessionFd(listenFd);
return false;
}
RefreshMaxFd(g_dataFd);
if (g_callback->onConnectEvent(g_dataFd, inet_ntoa(addrClient.sin_addr)) != 0) {
CloseAuthSessionFd(g_dataFd);
}
}
if (g_dataFd > 0 && FD_ISSET(g_dataFd, readSet)) {
g_callback->onDataEvent(g_dataFd);
}
return true;
}
無論是新連接請求,還是已有連接中有數據到來,均會進入本函數。
函數通過FD_ISSET()判斷是否是listenFd上存在消息,如果是,則說明當前存在新的連接,這時調用accept()完成鏈接創建,新創建的socket的fd被存儲在g_dataFd中,同時調用g_callback->onConnectEvent通知認證模塊有新的連接事件發生,並將新創建的fd和client的IP地址告知認證模塊。
與此同時,創建g_dataFd時候需要刷新g_maxFd,以保證在WaitProcess()中的下一次select()操作時中,會監聽到g_dataFd上的事件。
如果FD_ISSET()判斷出g_dataFd上存在消息,則說明已完成握手的連接向本節點發送了數據,這時函數回調g_callback->onDataEvent(),把控制權返回給調用者,以處理接收到的數據。
- 回調函數的處理
trans_service模塊的使用者設置的回調函數將在存在新連接、和新數據時被調用,比如認證模塊通過以下函數完成認證動作:OnConnectEvent()函數中完成對新連接的處理, OnDataEvent()函數中完成對新數據的處理。
int OnConnectEvent(int fd, const char *ip)
{
ProcessConnectEvent(fd, ip);
return 0;
}
int OnDataEvent(int fd)
{
ProcessDataEvent(fd);
return 0;
}
- 業務的session管理
該部分代碼負責業務的數據通信,節點通過名稱進行通信,對外隱藏了端口信息
authmanager模塊
設備之間互聯是基於系統的IoT設備(如AI音箱、智能家居、智能穿戴等設備)與IoT主控設備(手機、平板等)間建立點對點的信任關系,並在具備信任關系的設備間,搭建安全的連接通道,實現用戶數據端到端加密傳輸。
IoT主控設備和IoT設備建立點對點信任關系的過程,實際上是相互交換IoT設備的身份標識的過程。
authmanager是openharmony為設備提供認證機制的模塊,模塊內的處理流程為
為了實現用戶數據在設備互聯場景下在各個設備之間的安全流轉,需要保證設備之間相互正確可信,即設備和設備之間建立信任關系,並能夠在驗證信任關系后,搭建安全的連接通道,實現用戶數據的安全傳輸。設備之間的信任關系在本文檔中涉及IoT主控設備和IoT設備之間建立的可信關系。設備間可信關系建立的流程如下圖所示:
-
IoT設備互聯安全
設備互聯支持基於HarmonyOS的IoT設備(如AI音箱、智能家居、智能穿戴等設備)與IoT主控設備間建立點對點的信任關系,並在具備信任關系的設備間,搭建安全的連接通道,實現用戶數據端到端加密傳輸。 -
IoT主控設備的IoT業務身份標識
IoT主控設備為不同的IoT設備管理業務生成不同的身份標識,形成不同IoT管理業務間的隔離,該標識用於IoT主控設備與IoT設備之間的認證以及通信。IoT業務身份標識為橢圓曲線公私鑰對(Ed25519公私鑰對)。 -
IoT設備身份標識
IoT設備會生成各自的設備身份標識,用來與IoT主控設備通信。該身份標識同樣為橢圓曲線公私鑰對(Ed25519公私鑰對);IoT設備私鑰不出IoT設備,設備每次恢復出廠設置,會重置這個公私鑰對。
上述身份標識可用於IoT主控設備與IoT設備間的安全通信:當IoT主控設備與IoT設備通過信任綁定流程交換業務身份標識或設備標識后,可以進行密鑰協商並建立安全通信通道。 -
設備間點對點的信任綁定
IoT主控設備和IoT設備建立點對點信任關系的過程,實際上是相互交換IoT設備的身份標識的過程。
在點對點建立信任關系的過程中,用戶需要在IoT主控設備上,輸入IoT設備上提供的PIN碼:對於有屏幕的設備,該PIN碼動態生成;對於沒有屏幕的設備,該PIN碼由設備生產廠家預置;PIN碼的展示形式,可以是一個用戶可讀的數字,也可以是一個二維碼。隨后,IoT主控設備和IoT設備間使用PAKE協議完成認證和會話密鑰協商過程,並在此基礎上,通過協商出的會話密鑰加密傳輸通道用於交換雙方設備的身份標識公鑰。 -
IoT主控設備與IoT設備間的通信安全
當建立過信任關系的IoT主控設備與IoT設備間進行通信時,雙方在完成上述信任關系綁定后,基於本地存儲的對端身份公鑰相互進行認證;在每次通信時基於STS協議完成雙向身份認證以及會話密鑰協商,之后設備使用此會話密鑰加密雙方設備間的傳輸通道。
流程詳解與關鍵代碼數據結構:
初始化代碼流程如下圖:
該部分主要由discover 模塊調用BusManager 函數開始,若flag = 1,則進行StartBus 函數的執行,它的實現主要是調用了StartListener 函數及StartSession 函數,實現對設備進行認證及加解密的過程。
StartSession 函數如下:
int StartSession(const char *ip)
{
int port = CreateTcpSessionMgr(true, ip);
return port;
}
int CreateTcpSessionMgr(bool asServer, const char* localIp)
{
if (g_sessionMgr != NULL || localIp == NULL) {
return TRANS_FAILED;
}
g_sessionMgr = malloc(sizeof(TcpSessionMgr));
if (g_sessionMgr == NULL) {
return TRANS_FAILED;
}
(void)memset_s(g_sessionMgr, sizeof(TcpSessionMgr), 0, sizeof(TcpSessionMgr));
g_sessionMgr->asServer = asServer;
g_sessionMgr->listenFd = -1;
g_sessionMgr->isSelectLoopRunning = false;
if (InitTcpMgrLock() != 0 || GetTcpMgrLock() != 0) {
FreeSessionMgr();
return TRANS_FAILED;
}
for (int i = 0; i < MAX_SESSION_SUM_NUM; i++) {
g_sessionMgr->sessionMap_[i] = NULL;
}
for (int i = 0; i < MAX_SESSION_SERVER_NUM; i++) {
g_sessionMgr->serverListenerMap[i] = NULL;
}
if (ReleaseTcpMgrLock() != 0) {
FreeSessionMgr();
return TRANS_FAILED;
}
int listenFd = OpenTcpServer(localIp, DEFAULT_TRANS_PORT);
if (listenFd < 0) {
SOFTBUS_PRINT("[TRANS] CreateTcpSessionMgr OpenTcpServer fail\n");
FreeSessionMgr();
return TRANS_FAILED;
}
int rc = listen(listenFd, LISTEN_BACKLOG);
if (rc != 0) {
SOFTBUS_PRINT("[TRANS] CreateTcpSessionMgr listen fail\n");
CloseSession(listenFd);
FreeSessionMgr();
return TRANS_FAILED;
}
g_sessionMgr->listenFd = listenFd;
signal(SIGPIPE, SIG_IGN);
if (StartSelectLoop(g_sessionMgr) != 0) {
SOFTBUS_PRINT("[TRANS] CreateTcpSessionMgr StartSelectLoop fail\n");
CloseSession(listenFd);
FreeSessionMgr();
return TRANS_FAILED;
}
return GetSockPort(listenFd);
}
StartSession 該函數只有一個參數,即const char *ip,也就是一個IP,和StartListener函數中的IP 是一樣的。該函數是為全局變量g_sessionMgr 申請空間及初始化,然后根據所給的參數創建socket 文件描述符並監聽,之后通過調用StartSelectLoop 函數創建SelectSessionLoop 的線程,該線程將socket 文件描述符加入集合,並調用select 函數進行監控,若函數的返回值大於0,則調用ProcessData 函數,該函數有兩個分支,若socket 未創建session則為其創建session;若已創建session,則處理其數據部分。
當初步建立信任關系的IoT主控設備與IoT設備間在進行通信時,雙方首先完成信任關系綁定,然后基於存儲在本地的對端身份公鑰相互進行認證;在每次通信時完成雙向身份認證以及會話密鑰協商,之后設備使用此會話密鑰來解密雙方設備間的傳輸通道。
discovery模塊
discovery目錄主要提供設備發現功能,采用的協議則是COAP
,該目錄文件結構如下:
discovery\discovery_service\include\common_info_manager.h中,定義了目前鴻蒙OS支持的設備類型和設備級別,從定義不難看出,目前鴻蒙OS只支持L0和L1的設備。
#define DEVICE_TYPE_PHONE "PHONE"
#define DEVICE_TYPE_PAD "PAD"
#define DEVICE_TYPE_TV "TV"
#define DEVICE_TYPE_PC "PC"
#define DEVICE_TYPE_AUDIO "AUDIO"
#define DEVICE_TYPE_CAR "CAR"
#define L0_DEVICE_NAME "DEV_L0"
#define L1_DEVICE_NAME "DEV_L1"
用戶使用發現功能時,需要保證發現端設備與被發現端設備在同一個局域網內,並且互相能收到對方以下流程的報文。
(1)發現端設備,發起discover請求后,使用coap協議在局域網內發送廣播。
(2)被發現端設備使用PublishService接口發布服務,接收端收到廣播后,發送coap協議單播給發現端。
(3)發現端設備收到報文會更新設備信息。
下面是設備的定義,分為本地設備(被發現端設備)和外部設備,從中不難看出鴻蒙OS的分布式特性,本地設備可以通過PublishService函數將自身的服務發布出去,供外部設備發現並使用。鴻蒙OS的設備發現機制是被發現設備在COAP端口監聽來自發現設備的廣播包,這也符合按需使用的原則。
typedef enum {
ONLINE = 0,
OFFLINE,
} NetworkState;
typedef struct DeviceInfo {
char deviceName[MAX_DEV_NAME_LEN];
char deviceId[MAX_DEV_ID_LEN];
char deviceIp[MAX_DEV_IP_LEN];
char version[MAX_DEV_VERSION_LEN];
char softwareVersion[MAX_SOFTWARE_VERSION_LEN];
char networkName[MAX_DEV_NETWORK_LEN];
int deviceType;
int devicePort;
NetworkState networkState;
int isAccountTrusted;
} DeviceInfo;
Discovery 對外提供PublishService() 接口來實現設備的發現功能,其函數實現解讀如下:
PublishService主要的代碼流程圖如下:
輕量設備主要承擔服務發布者,也就是被發現端的功能。被發現端主要是通過PublishService()這個函數發布服務,然后在服務發布成功后的回調中使用CreateSessionServer()函數來創建會話服務器等待發現端的連接。這個章節我們主要從代碼分析PublishService()這個API的實現過程。
PublishService()函數的實現在discovery_service.c文件中,基本上其他所有的c源代碼的實現都是為這個函數提供支撐的。代碼如下:
int PublishService(const char *moduleName, const struct PublishInfo *info, const struct IPublishCallback *cb)
{
// 權限檢查
if (SoftBusCheckPermission(SOFTBUS_PERMISSION) != 0 || info == NULL || cb == NULL) {
SOFTBUS_PRINT("[DISCOVERY] PublishService invalid para(info or cb)\n");
return ERROR_INVALID;
}
// 函數參數有效性校驗
if (moduleName == NULL || strlen(moduleName) >= MAX_PACKAGE_NAME || info->publishId <= 0 ||
info->dataLen > MAX_CAPABILITY_DATA_LEN) {
SOFTBUS_PRINT("[DISCOVERY] PublishService invliad para\n");
PublishCallback(info->publishId, PUBLISH_FAIL_REASON_PARAMETER_INVALID, NULL, cb);
return ERROR_INVALID;
}
if (info->medium != COAP) {
PublishCallback(info->publishId, PUBLISH_FAIL_REASON_NOT_SUPPORT_MEDIUM, NULL, cb);
return ERROR_INVALID;
}
// 創建信號量
if (g_serviceSemId == INVALID_SEM_ID) {
if (SemCreate(0, &g_serviceSemId) != 0) {
g_serviceSemId = INVALID_SEM_ID;
PublishCallback(info->publishId, PUBLISH_FAIL_REASON_UNKNOWN, NULL, cb);
return ERROR_FAIL;
}
}
// 初始化服務
(void)SemWait(&g_serviceSemId);
if (InitService() != ERROR_SUCCESS) {
SOFTBUS_PRINT("[DISCOVERY] PublishService InitService fail\n");
PublishCallback(info->publishId, PUBLISH_FAIL_REASON_UNKNOWN, NULL, cb);
(void)SemPost(&g_serviceSemId);
return ERROR_FAIL;
}
// 將Publish信息加入到Module列表
PublishModule *findModule = AddPublishModule(moduleName, info);
if (findModule == NULL) {
SOFTBUS_PRINT("[DISCOVERY] PublishService AddPublishModule fail\n");
PublishCallback(info->publishId, PUBLISH_FAIL_REASON_UNKNOWN, NULL, cb);
(void)SemPost(&g_serviceSemId);
return ERROR_FAIL;
}
// 注冊COAP服務
int ret = ERROR_SUCCESS;
if (info->capability == NULL || info->capabilityData == NULL) {
(void)CoapRegisterDefualtService();
} else {
ret = DoRegistService(info->medium);
}
(void)SemPost(&g_serviceSemId);
//回調發布成功
if (ret != ERROR_SUCCESS) {
PublishCallback(info->publishId, PUBLISH_FAIL_REASON_UNKNOWN, findModule, cb);
return ERROR_FAIL;
} else {
PublishCallback(info->publishId, ERROR_SUCCESS, findModule, cb);
return ERROR_SUCCESS;
}
}
該代碼由七個if語句組成,因此大致分為7個部分。每部分功能已在代碼中注釋。
其中第二部分是輸出參數有效性檢查
。輸入參數總共是3個,分別是:
- moduleName:調用者的模塊名稱子串
- info:PublishInfo結構體,發布的信息
- cb:發布成功或者失敗的回調函數
上面的代碼可以看到分別對moduleName是否為空,子串的長度,info里面的publishId、dataLen等進行了有效性檢查。如果有問題,那么會調用 PublishCallback()來回調到cb里面的失敗回調函數,並且給出了出錯碼。
第四部分為初始化代碼,代碼中已經給出注釋:
int InitService(void)
{
/**是否已經初始化過了
g_isServiceInit全局變量顯示是否已經初始化過了,
如果已經被別的模塊調用PublishService(),
那么這個變量的值將為1,那么不需要第二次初始化了,直接返回。*/
if (g_isServiceInit != 0) {
return ERROR_SUCCESS;
}
/*初始化Common Manager(初始化g_deviceInfo結構體)*/
if (InitCommonManager() != 0) {
SOFTBUS_PRINT("[DISCOVERY] InitService InitCommonManager fail\n");
DeinitService();
return ERROR_FAIL;
}
/* 為內部使用的數據結構分配內存
g_publishModule 這個全局變量保存所有發布服務的模塊的信息數組。*/
g_publishModule = calloc(1, sizeof(PublishModule) * MAX_MODULE_COUNT);
if (g_publishModule == NULL) {
DeinitService();
return ERROR_NOMEMORY;
}
g_capabilityData = calloc(1, MAX_SERVICE_DATA_LEN);
if (g_capabilityData == NULL) {
DeinitService();
return ERROR_NOMEMORY;
}
/*注冊wifi Callback*/
RegisterWifiCallback(WifiEventTrigger);
/*COAP初始化,注冊TCP/IP協議棧的處理,
注冊session的底層socket的處理*/
int ret = CoapInit();
if (ret != ERROR_SUCCESS) {
SOFTBUS_PRINT("[DISCOVERY] InitService CoapInit fail\n");
DeinitService();
return ret;
}
/*調用CoapWriteMsgQueue()觸發獲取wifi的IP地址,並啟動總線*/
#if defined(__LITEOS_M__) || defined(__LITEOS_RISCV__)
CoapWriteMsgQueue(UPDATE_IP_EVENT);
#endif
/*向COAP中注冊設備信息*/
ret = CoapRegisterDeviceInfo();
if (ret != ERROR_SUCCESS) {
SOFTBUS_PRINT("[DISCOVERY] InitService CoapRegisterDeviceInfo fail\n");
DeinitService();
return ret;
}
g_isServiceInit = 1;
#if defined(__LITEOS_A__) || defined(__LINUX__)
if (BusManager(1) != ERROR_SUCCESS) {
SOFTBUS_PRINT("[DISCOVERY] InitService BusManager(1) fail\n");
}
#endif
SOFTBUS_PRINT("[DISCOVERY] InitService ok\n");
return ERROR_SUCCESS;
}
整個InitService的時序圖如下:
初始化服務調用InitService()函數實現了如下的功能:
-
初始化了g_deviceInfo結構體,包括:deviceName,deviceId,deviceIp。
-
注冊wifi_lite的event監控事件,當wifi鏈路發生變化的情況下(例如:設備wifi連接成功),獲取當前設備wifi的IP地址,並放入到g_deviceInfo->deviceIp中,為下面TCP/IP協議棧的初始化做准備。
-
初始化UDP協議socket綁定在COAP_DEFAULT_PORT端口上,監聽COAP的discover消息,並進行處理。
-
根據deviceIp初始化TCP協議socket,並且啟動一個監聽任務,處理連接請求,並進行auth的校驗。
-
根據deviceIp初始化TCP協議socket,並且啟動一個監聽任務,處理session的會話請求。
設備發現整體流程總結:
-
設備發現部分代碼(主要是輕量設備側)主入口函數為PublishService()函數。
-
PublishService()函數會檢查軟總線的服務是否已經初始化過,如果沒有則會初始化軟總線的所有服務,包括:a、基於UDP的COAP協議discover發現服務。b、wifi設備狀態監聽服務。c、基於TCP的認證服務。d、基於TCP的session會話管理服務。
-
PublishService()然后會把模塊的信息加入g_publishModule全局數組中。
-
回調模塊的發布成功回調函數。
編譯測試
環境搭建
在進行源碼編譯之前,先進行編譯環境的搭建[4]。
-
安裝nodejs和npm
sudo apt-get install nodejs
sudo apt install libssl1.0-dev nodejs-dev node-gyp npm
-
通過Node.js自帶的npm安裝hpm-cli命令行工具。執行以下命令:
npm install -g @ohos/hpm-cli
-
安裝完成后執行如下命令,顯示hpm版本,即安裝成功。
hpm -V 或 hpm --version
-
安裝開發依賴的組件
hpm包管理器將常用開發開發工具(如燒錄,編譯,壓縮等)也發布成了組件。可以通過如下命令方式進行安裝,執行完該命令后,系統會自動將開發依賴的工具下載安裝,且這些組件只需全局安裝一次。hpm i -g @ohos/llvm hpm i -g @ohos/ninja hpm i -g @ohos/gn hpm i -g @ohos/hc_gen hpm i -g @ohos/sysroot
這是一組開發工具的組件包(如包含gn,ninja等工具),有了這些開發態的組件,就可以進行常規的源碼組件的開發了。
-
將linux shell改為bash:
ls -l $(which sh) # 如果指向的不是bash,則按以下方式修改: # 方法一:執行以下命令,然后選擇no dpkg-reconfigure dash # 方法二:先刪除sh,再重新創建軟連接 rm -f /bin/sh ln -s bash /bin/sh
-
確認python3.7以上版本
准備編譯
查看軟總線模塊依賴文件
cat BUILD.gn
其中:
include_dirs = [
"//foundation/communication/services/softbus_lite/discovery/coap/include",
"//foundation/communication/services/softbus_lite/os_adapter/include",
"//foundation/communication/interfaces/kits/softbus_lite/discovery",
"//third_party/cJSON",
"//third_party/bounds_checking_function/include",
"//foundation/communication/services/softbus_lite/discovery/discovery_service/include",
"//foundation/communication/services/softbus_lite/authmanager/include",
"//base/startup/interfaces/kits/syspara_lite",
"//foundation/communication/services/softbus_lite/trans_service/include/libdistbus",
"//foundation/communication/services/softbus_lite/trans_service/include/utils",
"//foundation/communication/services/softbus_lite/trans_service/source/libdistbus",
"//foundation/communication/services/softbus_lite/trans_service/source/utils",
"//kernel/liteos_a/lib/libsec/include",
"//foundation/communication/interfaces/kits/softbus_lite/transport",
"//base/security/interfaces/innerkits/hichainsdk_lite",
"//third_party/mbedtls/include",
"//base/security/frameworks/hichainsdk_lite/source/huks_adapter/",
"//base/security/interfaces/kits/iam_lite"
]
softbus_lite_sources = [
"//foundation/communication/services/softbus_lite/discovery/coap/source/coap_discover.c",
"//foundation/communication/services/softbus_lite/discovery/coap/source/json_payload.c",
"//foundation/communication/services/softbus_lite/discovery/coap/source/nstackx_common.c",
"//foundation/communication/services/softbus_lite/discovery/coap/source/nstackx_device.c",
"//foundation/communication/services/softbus_lite/discovery/coap/source/coap_socket.c",
"//foundation/communication/services/softbus_lite/discovery/coap/source/coap_adapter.c",
"//foundation/communication/services/softbus_lite/os_adapter/source/L1/os_adapter.c",
"//foundation/communication/services/softbus_lite/discovery/discovery_service/source/discovery_service.c",
"//foundation/communication/services/softbus_lite/discovery/discovery_service/source/coap_service.c",
"//foundation/communication/services/softbus_lite/discovery/discovery_service/source/common_info_manager.c",
"//foundation/communication/services/softbus_lite/trans_service/source/libdistbus/tcp_session.c",
"//foundation/communication/services/softbus_lite/trans_service/source/libdistbus/tcp_session_manager.c",
"//foundation/communication/services/softbus_lite/trans_service/source/libdistbus/auth_conn_manager.c",
"//foundation/communication/services/softbus_lite/trans_service/source/libdistbus/trans_lock.c",
"//foundation/communication/services/softbus_lite/trans_service/source/utils/tcp_socket.c",
"//foundation/communication/services/softbus_lite/trans_service/source/utils/message.c",
"//foundation/communication/services/softbus_lite/trans_service/source/utils/aes_gcm.c",
"//foundation/communication/services/softbus_lite/authmanager/source/auth_conn.c",
"//foundation/communication/services/softbus_lite/authmanager/source/auth_interface.c",
"//foundation/communication/services/softbus_lite/authmanager/source/msg_get_deviceid.c",
"//foundation/communication/services/softbus_lite/authmanager/source/wifi_auth_manager.c",
"//foundation/communication/services/softbus_lite/authmanager/source/bus_manager.c",
]
include_dirs包括了編譯該組件所需頭文件
softbus_lite_sources包括了編譯組件的源代碼
根據上述依賴建立makefile文件,方便起見,makefile文件建立在~/openharmony目錄下。
ROOT_DIR:= ~/openharmony
INC_DIR:= \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/coap/include \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/os_adapter/include \
$(ROOT_DIR)/foundation/communication/interfaces/kits/softbus_lite/discovery \
$(ROOT_DIR)/third_party/cJSON \
$(ROOT_DIR)/third_party/bounds_checking_function/include \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/discovery_service/include \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/authmanager/include \
$(ROOT_DIR)/base/startup/interfaces/kits/syspara_lite \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/include/libdistbus \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/include/utils \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/source/libdistbus \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/source/utils \
$(ROOT_DIR)/kernel/liteos_a/lib/libsec/include \
$(ROOT_DIR)/foundation/communication/interfaces/kits/softbus_lite/transport \
$(ROOT_DIR)/base/security/interfaces/innerkits/hichainsdk_lite \
$(ROOT_DIR)/third_party/mbedtls/include \
$(ROOT_DIR)/base/security/frameworks/hichainsdk_lite/source/huks_adapter \
$(ROOT_DIR)/base/security/interfaces/kits/iam_lite \
$(ROOT_DIR)/foundation/communication/interfaces/kits/softbus_lite/discovery/ \
SRCS:= \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/authmanager/source/auth_conn.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/authmanager/source/auth_interface.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/authmanager/source/bus_manager.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/authmanager/source/msg_get_deviceid.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/authmanager/source/wifi_auth_manager.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/coap/source/coap_adapter.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/coap/source/coap_discover.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/coap/source/coap_socket.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/coap/source/json_payload.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/coap/source/nstackx_common.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/coap/source/nstackx_device.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/discovery_service/source/coap_service.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/discovery_service/source/common_info_manager.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/discovery/discovery_service/source/discovery_service.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/os_adapter/source/L1/os_adapter.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/source/libdistbus/auth_conn_manager.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/source/libdistbus/tcp_session.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/source/libdistbus/tcp_session_manager.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/source/libdistbus/trans_lock.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/source/utils/aes_gcm.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/source/utils/message.c \
$(ROOT_DIR)/foundation/communication/services/softbus_lite/trans_service/source/utils/tcp_socket.c \
OBJS:= $(patsubst %.c, %.o, $(SRCS))
LIBS:=
CC:=gcc
CXXFLAGS:= -fPIC -Wall $(addprefix -I , $(INC_DIR)) $(LIBS) -Wno-deprecated
all:
$(CC) -c $(SRCS) $(CXXFLAGS)
rm -rf ./obj
mv *.o ./obj/
$(CC) -shared -o ./obj/libsoftbus_lite.so ./obj/*.o
clean:
rm -rf ./obj
測試
新建測試demo
//demo.c
#include<discovery_service.h>
#include<session.h>
#include<coap_discover.h>
#include<tcp_session_manager.h>
#include<nstackx.h>
#include<stdio.h>
#include<string.h>
// 定義業務⾃身的業務名稱,會話名稱及相關回調
const char *g_pkgName = "BUSINESS_NAME";
const char *g_sessionName = "SESSION_NAME";
struct ISessionListener * g_sessionCallback= NULL;
#define NAME_LENGTH 64
#define TRANS_FAILED -1
// 回調實現:接收對方通過SendBytes發送的數據,此示例實現是接收到對端發送的數據后回復固定消息
void OnBytesReceivedTest(int sessionId, const void* data, unsigned int dataLen)
{
printf("OnBytesReceivedTest\n");
printf("Recv Data: %s\n", (char *)data);
printf("Recv Data dataLen: %d\n", dataLen);
char *testSendData = "Hello World, Hello!";
SendBytes(sessionId, testSendData, strlen(testSendData));
return;
}
// 回調實現:用於處理會話關閉后的相關業務操作,如釋放當前會話相關的業務資源,會話無需業務主動釋放
void OnSessionClosedEventTest(int sessionId)
{
printf("Close session successfully, sessionId=%d\n", sessionId);
}
// 回調實現:用於處理會話打開后的相關業務操作。返回值為0,表示接收;反之,非0表示拒絕。此示例表示只接受其他設備的同名會話連接
int OnSessionOpenedEventTest(int sessionId)
{
char sessionNameBuffer[NAME_LENGTH+1];
if(GetPeerSessionName(sessionId,sessionNameBuffer,NAME_LENGTH) == TRANS_FAILED) {
printf("GetPeerSessionName faild, which sessionId = %d\n",sessionId);
return -1;
}
if (strcmp(sessionNameBuffer, g_sessionName) != 0) {
printf("Reject the session which name is different from mine, sessionId=%d\n", sessionId);
return -1;
}
printf("Open session successfully, sessionId=%d\n", sessionId);
return 0;
}
// 向SoftBus注冊業務會話服務及其回調
int StartSessionServer()
{
if (g_sessionCallback == NULL) {
g_sessionCallback = (struct ISessionListener*)malloc(sizeof(struct ISessionListener));
}
if (g_sessionCallback == NULL) {
printf("Failed to malloc g_sessionCallback!\n");
return -1;
}
g_sessionCallback->onBytesReceived = OnBytesReceivedTest;
g_sessionCallback->onSessionOpened = OnSessionOpenedEventTest;
g_sessionCallback->onSessionClosed = OnSessionClosedEventTest;
int ret = CreateSessionServer(g_pkgName, g_sessionName, g_sessionCallback);
if (ret < 0) {
printf("Failed to create session server!\n");
free(g_sessionCallback);
g_sessionCallback = NULL;
}
return ret;
}
// 從SoftBus中刪除業務會話服務及其回調
void StopSessionServer()
{
int ret = RemoveSessionServer(g_pkgName, g_sessionName);
if (ret < 0) {
printf("Failed to remove session server!\n");
return;
}
if (g_sessionCallback != NULL) {
free(g_sessionCallback);
g_sessionCallback = NULL;
}
}
// 回調函數聲明:
void onSuccess(int publishId)
{
printf("publish succeeded, publishId = %d\r\n", publishId);
char ipbuff[NSTACKX_MAX_IP_STRING_LEN] = {"0.0.0.0"};
CoapGetIp(ipbuff,NSTACKX_MAX_IP_STRING_LEN,0);
printf("CoapGetIp = %s\n",ipbuff);
if(StartSessionServer()!=-1)
printf("StartSessionServer successed!\n");
}
void onFail(int publishId, PublishFailReason reason)
{
printf("publish failed, publishId = %d, reason = %d\r\n", publishId, reason);
}
int main(){
// 服務發布接口使用
PublishInfo info = {0};
IPublishCallback cb = {0};
cb.onPublishSuccess = onSuccess;
cb.onPublishFail = onFail;
char a[] = "456";
info.capabilityData = a;
info.capability = "ddmpCapability";
info.dataLen = strlen(a);
info.medium = 2;
info.publishId = 1;
PublishService("cxx", &info, &cb);
}
將前面編譯生成的鏈接文件放入系統鏈接庫中。
動態編譯測試文件,生成可執行文件執行:
gcc demo.c -o demo -g -lsoftbus_lite -lrt -lpthread
./demo
效果如下: