一.HarmonyOS概述
1.1 系統定義
HarmonyOS是一款“面向未來”、面向全場景(移動辦公、運動健康、社交通信、媒體娛樂)的分布式操作系統。在傳統的單設備系統能力的基礎上,HarmonyOS提出了基於同一套系統能力、適配多種終端形態的分布式理念,能夠支持手機、平板、智能穿戴。智慧屏、車機等多種終端設備。
- 對消費者而言,HarmonyOS 能夠將生活場景中的各類終端進行能力整合,形成一個“超級虛擬終端”,可以實現不同的終端設備之間的快速連接、能力互助、資源共享,匹配合適的設備、提供流暢的全場景體驗
- 對應用開發者而言,HarmonyOS 采用了多種分布式技術,使得應用程序的開發實現與不同終端設備的形態差異無關,降低了開發難度和成本。這能夠讓開發者聚焦上層業務邏輯,更加便捷、高效地開發應用。
- 對設備開發者而言,HarmonyOS 采用了組件化的設計方案,可以根據設備的資源能力和業務特征進行靈活裁剪,滿足不同形態的終端設備對於操作系統的要求。
1.2 技術特性
HarmonyOS系統的技術特性包含以下幾種
1.2.1 硬件互助、資源共享
- 分布式軟總線:是多種終端設備的統一基座,為設備之間的互聯互通提供了統一的分布式通信能力,能夠快速發現並連接設備,高效地分發任務和傳輸數據。
- 分布式設備虛擬化:可以實現不同設備的資源融合、設備管理、數據處理,多種設備共同形成一個超級虛擬終端。針對不同類型的任務,為用戶匹配並選擇能力合適的執行硬件,讓業務連續地在不同設備間流轉,充分發揮不同設備的資源優勢。
- 分布式數據管理:基於分布式軟總線的能力,實現應用程序數據和用戶數據的分布式管理。
- 分布式任務調度:基於分布式軟總線、分布式數據管理、分布式 Profile 等技術特性,構建統一的分布式服務管理(發現、同步、注冊、調用)機制,支持對跨設備的應用進行遠程啟動、遠程調用、遠程連接以及遷移等操作,能夠根據不同設備的能力、位置、業務運行狀態、資源使用情況,以及用戶的習慣和意圖,選擇合適的設備運行分布式任務。
1.2.2 一次開發,多端部署
HarmonyOS 提供了用戶程序框架、Ability 框架以及 UI 框架,支持應用開發過程中多終端的業務邏輯和界面邏輯進行復用,能夠實現應用的一次開發、多端部署,提升了跨設備應用的開發效率。
1.2.3 統一OS,彈性部署
HarmonyOS 通過組件化和小型化等設計方法,支持多種終端設備按需彈性部署,能夠適配不同類別的硬件資源和功能需求。支撐通過編譯鏈關系去自動生成組件化的依賴關系,形成組件樹依賴圖,支撐產品系統的便捷開發,降低硬件設備的開發門檻。
- 支持各組件的選擇(組件可有可無):根據硬件的形態和需求,可以選擇所需的組件。
- 支持組件內功能集的配置(組件可大可小):根據硬件的資源情況和功能需求,可以選擇配置組件中的功能集。例如,選擇配置圖形框架組件中的部分控件。
- 支持組件間依賴的關聯(平台可大可小):根據編譯鏈關系,可以自動生成組件化的依賴關系。例如,選擇圖形框架組件,將會自動選擇依賴的圖形引擎組件等。
1.3 系統架構
HarmonyOS 整體遵從分層設計,從下向上依次為:內核層、系統服務層、框架層和應用層。系統功能按照“系統 > 子系統 > 功能/模塊
”逐級展開,在多設備部署場景下,支持根據實際需求裁剪某些非必要的子系統或功能/模塊。HarmonyOS 技術架構如圖所示。
二.分布式軟總線
2.1 總線與分布式軟總線
2.1.1 總線
總線是一種內部結構,它是cpu、內存、輸入、輸出設備傳遞信息的公用通道,主機的各個部件通過總線相連接,外部設備通過相應的接口電路再與總線相連接,從而形成了計算機硬件系統。
在計算機系統中,各個部件之間傳送信息的公共通路叫總線,微型計算機是以總線結構來連接各個功能部件的。
按照計算機所傳輸的信息種類,計算機的總線可以划分為數據總線
、地址總線
和控制總線
,分別用來傳輸數據
、數據地址
和控制信號
。
傳統總線的典型特征:
- 即插即用
- 高帶寬
- 低時延
- 高可靠
- 標准
2.1.2 分布式軟總線
相較於傳統計算機中的硬總線,鴻蒙系統中的分布式軟總線是一條虛擬的、“無形”的總線。可以連接同處於一個局域網內部的所有鴻蒙設備(1+8+N,如下圖所示),並且具有自發現
、自組網
、高帶寬
和低時延
等特點。
1+8+N
1:手機
8:車機、音箱、耳機、手表/手環、平板、大屏、PC、AR/VR
N:泛指其他IOT設備
2.2 異構網絡組網
軟總線技術支持對不同協議的異構網絡進行組網。傳統場景下,需要藍牙傳輸的兩台設備必須都具有藍牙,需要WiFi傳輸的設備必須都具有WiFi。而藍牙/WiFi 之間是無法進行數據通信的。
軟總線提出藍牙/WiFi 融合網絡組網技術(架構如下圖所示),解決了不同協議設備進行數據通信的問題。使得多個鴻蒙設備能夠自動構建一個邏輯全連接網絡,用戶或者業務開發者無需關心組網方式與物理協議。對於軟件開發者來說軟總線異構組網可以大大降低其開發成本。
2.3 軟總線傳輸
傳統協議的傳輸速率差異非常大,時延也難以得到保證。軟總線傳輸通過“極簡協議”實現以下三個目標:
- 高帶寬(High Speed)
- 低時延(Low Latency)
- 高可靠(High Reliability)
將中間的四層協議棧精簡為一層提升有效載荷,有效傳輸帶寬提升20%
極簡協議在傳統網絡協議的基礎上進行了增強,體現於以下幾個方面:
- 流式傳輸:基於UDP實現數據的保序和可靠傳輸;
- 雙輪驅動:顛覆傳統TCP每包確認機制;
- 不懼網損:摒棄傳統滑動窗口機制,丟包快速恢復,避免阻塞;
- 不懼抖動:智能感知網絡變化,自適應流量控制和擁塞控制;
2.4 分布式軟總線源碼結構
分布式軟中線代碼倉庫地址如下:
上述兩個倉庫分別對應它的接口和實現;
而communication_services_softbus_lite
源碼結構中,又分為authmanager
、discovery
、trans_service
和兼容系統差別的os_adapter
目錄。
discover
:提供基於 COAP 協議的設備發現機制;authmanager
:提供設備認證機制和知識庫管理功能;trans_service
:提供身份驗證和數據傳輸通道;os_adapter
:檢測運行設備性能,決定部分功能是否執行。
2.4.1 discover
discovery
單元提供了基於coap
(Constrained Application Protocol,受限應用協議,RFC7252)協議的設備發現機制。
考慮到運行HarmonyOS
的設備除了硬件性能較好的手機、電腦等設備,還有資源受限的物聯網設備,這些設備的ram、rom相對較小。coap 協議支持輕量的可靠傳輸,采用 coap 協議,可以擴大組網范圍。
discovery 的實現前提是確保發現端設備與接收端設備在同一個局域網內且能互相收到對方的報文。流程為以下三步:
- 發現端設備,使用
coap
協議在局域網內發送廣播; - 接收端設備使用
PublishService
接口發布服務,接收端收到廣播后,發送coap
協議單播給發現端; - 發現端設備收到回復單播報文,更新設備信息。
discovery
部分代碼由兩部分組成(目錄如下圖所示)。其中 coap
部分是 coap
協議的封裝實現, discovery_service
是基於 coap
協議的設備間發現流程的實現。
coap
- coap_def.h:定義 coap 協議包的格式、報文結構,且使用 UDP 協議傳輸;
- coap_adapter.c:實現 coap 協議的編碼、解碼函數;
- coap_socket.c:實現 coap 包的發現、接收服務;
- Coap_discovery.c:實現基於 coap 協議的設備發現功能。本文件定義了 socket通訊過程
discovery_service
- comman_info_manager.h:定義了鴻蒙系統當前支持的設備類型與級別;
- Discovery_service.c:實現了設備暴露、發現和連接流程。這里需要注意的是,考慮到同一局域網下,主設備發出連接請求廣播后,多個物聯網設備都會回復單播應答報文從而造成信道沖突。為避免此情況發生,每個物聯網設備均維護一套 信號量機制,實現多設備的有序等待。
2.4.2 authmanager
authmanager
單元提供了設備認證機制。設備通過加密和解密的方式,互相建立信任關系,確保在互聯場景下,用戶數據在正確的設備之間進行流轉,實現用戶數據的加密傳輸。軟總線中定義加密和解密算法的內容在 trans_service/utils/aes_gcm.h
中,authmanager
中的處理流程如下圖所示:
authmanager
- auth_conn.c:提供發送、接收、認證和獲取密鑰的功能;
- auth_interface.c:管理會話、鏈接、密鑰節點,提供增刪改查功能;
- msg_get_deviceid.c:以
cJSON
格式獲取各個設備的信息,包括設備 id、鏈接信息、設備名、設備類型等; - bus_manager.c:創建不同的
listen
,用以監聽系統上有哪些device
並創建新的device
節點,以及節點數據的處理。bus_manager.c
主要由discovery
單元調用,通過判斷本文件中flag
標志位決定是否啟動總線(start_bus()
函數)或關閉當前總線 (stop_bus()
函數)。discovery
調用后,bus_manager
執行流程如圖: - wifi_auth_manager.c:實現了鏈接管理和數據接收功能。
trans_service
經過第一階段:協議確定、設備發現,第二階段:設備鏈接,軟總線模塊執行到了第三階段:數據傳輸階段,即目錄中 trans_service
單元。trans_service
模塊依賴於 HarmonyOS
提供的網絡 socket
服務,向認證模塊提供認證通道管理和認證數據的收發;向業務模塊提供 session
管理和基於 session
的數據收發功能,並且通過 GCM
模塊的加密功能提供收發報文的加密/解密保護。如下圖所示為 trans_service
模塊在系統架構中的位置:
該部分源碼的結構及其功能如下:
在libdistbus
目錄中,各源碼文件的實現功能均已給出,其中需要注意的是,軟總線啟動后,設備在發現階段基於coap
協議使用udp
數據報進行通訊。當設備認證通過,確認連接后,將會使用TCP
協議進行更加安全可靠的通訊。同發現階段避免多設備同時向主機回復單播報文產生信道沖突一樣,這里也維護了一套信號量機制避免多設備互連產生資源沖突。
三.編譯與測試
3.1 基本環境
環境 | 版本 |
---|---|
Linux操作系統 | Ubuntu 20.04 |
gcc | 9.3.0 |
3.2 編譯過程
3.2.1 源碼獲取
本次項目源碼來源於碼雲的開源項目。通過下載OpenHarmony全量代碼進行實驗;
詳細源碼下載路徑可通過官方說明文檔獲取,下載地址:OpenHarmony開發者文檔
下載后,解壓文件即可獲得全量代碼,其中軟總線部分代碼路徑如下
OpenHarmony/foundation/communication/services/softbus_lite/
3.2.2 前期准備
將linux shell修改為bash
ls -l /bin/sh
sudo dpkg-reconfigure dash # 選擇 No
查看軟總線依賴項
首先進入軟總線代碼路徑,查看依賴文件BUILD.gn
cd OpenHarmony/foundation/communication/services/softbus_lite/
cat BUILD.gn
其中:
include_dirs
包括了編譯該組件所需頭文件softbus_lite_sources
包括了編譯組件的源代碼
編寫相應Makefile文件
根據個人路徑不同,需要修改Makefile文件首部ROOT_DIR的具體路徑
ROOT_DIR:=/home/jeff/workspace/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
編譯並查看結果
make
3.3 測試
3.3.1 參考官網文檔,編寫測試文件
文檔地址:分布式通信子系統
#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);
}
3.3.2 編譯運行
gcc -o publshiServer DemoTest.c -lsoftbus_lite -lrt -lphread
四.源碼分析
4.1 總體概述
4.1.1 設備發現
在分布式軟總線子系統中,設備分為發現端和被發現端。
- 發現端:請求使用服務的設備。一般指智慧屏設備。
- 被發現端:發布服務的設備。一般指輕量設備。
約束:目前必須保證發現端和被發現端處於同一個局域網內。
-
發現端設備,發起
discover
請求后,使用coap協議
在局域網內發送廣播。報文如下:
-
被發現端設備使用
PublishService
接口發布服務,接收端收到廣播后,發送coap協議
單播給發現端。報文格式如下:
-
發現端設備收到報文會更新設備信息。
被發現端發布服務的例子代碼如下:
與上述測試中的代碼相似
發現的流程圖如下:
4.1.2 數據傳輸
軟總線提供統一的基於Session
的傳輸功能,業務可以通過sessionId
收發數據或獲取其相關基本屬性。當前本項目只實現被動接收Session
連接的功能,業務可根據自身需要及Session
自身屬性判斷是否接受此Session
,如不接受,可以主動拒絕此連接。時序圖,大致的如下:
- 上面圖中
Module
是指使用軟總線的任意模塊(比如:分布式調度子系統模塊),onPublishServiceDone()
回調函數是在PushService()
調用中設置的回調函數。 - 在成功發布后,軟總線會調用這個回調。然后
Module
可以在這個回調中調用上面代碼中的StartSessionServer()
函數,在StartSessionServer()
函數中,調用軟總線的接口CreateSessionServer()
創建會話服務,等待其他設備的會話連接。 - 當其他設備會話連接成功后,軟總線會首先調用
OnSessionOpenedEventTest()
函數,然后在數據傳輸完成后調用OnBytesReceivedTest()
回調函數。Module
可以在OnBytesReceivedTest()
中處理數據協議格式的解析以及對於的Command
命令字的功能調用。
4.2 代碼分析
4.2.1 設備發現
PublishService()
函數的實現在discovery_service.c
文件中, 這個函數大致可以分成7個部分,下面我們分別分析這個7個部分的代碼。
整體流程圖如下:
① 權限檢查
SoftBusCheckPermission()
函數實現在os_adapter
目錄中。
為了適配不同的底層操作系統,os_adapter.c
中封裝了一些函數
-
source/L0/os_adapter.c:適配LiteOS
在LiteOS中基本沒做權限的判斷,只是檢查參數是否有效。 -
source/L1/os_adapter.c:適配Linux
增添了CheckPermission()
函數,使用API來進行權限判斷。
② 函數參數有效性校驗
第二部分是輸出參數有效性檢查。輸入參數總共是3個,分別是:
- moduleName:調用者的模塊名稱子串
- info:
PublishInfo
結構體,發布的信息 - cb:發布成功或者失敗的回調函數
上面的代碼可以看到分別對moduleName
是否為空,子串的長度,info
里面的publishId
、dataLen
等進行了有效性檢查。如果有問題,那么會調用 PublishCallback()
來回調到cb
里面的失敗回調函數,並且給出了出錯碼。
③ 創建信號量
④ 初始化服務
InitService()
的代碼:
A.是否已經初始化
g_isServiceInit
全局變量顯示是否已經初始化,如果已經被別的模塊調用PublishService()
,那么這個變量的值將為1,那么不需要第二次初始化,直接返回。
B.初始化Common Manager
(初始化g_deviceInfo
結構體)
InitCommonManager()
實現如下:
InitLocalDeviceInfo()
函數主要是把g_deviceInfo
結構體初始化好。g_deviceInfo
中幾個主要的成員如下:
deviceName
:對於L0設備為DEV_L0version
:固定為 “1.0.0”deviceId
:通過函數GetDeviceIdFromFile()
調用取得。這個函數會從/storage/data/softbus/deviceid
文件中讀取,如果讀取不到,那么使用隨機數字符串組成deviceId
,然后再寫入到上面的文件中。
C.為內部使用的數據結構分配內存
g_publishModule
這個全局變量保存所有發布服務的模塊的信息數組。這個數組的元素的數據結構如下:
上面的數據結構中的成員內容基本都是從PublishInfo
結構體來的:
D.注冊wifi Callback
RegisterWifiCallback()
函數的實現如下:
E.COAP初始化,注冊TCP/IP協議棧的處理,注冊session的底層socket的處理
在InitService()函數中,調用了CoapInit()函數,代碼如下:
深入查看NSTACKX_Init()
部分如下:
進一步深入CoapInitDiscovery()
部分,大體分為三個部分:
-
CoapInitSocket()
CoapInitSocket()
函數調用了CoapCreateUdpServer()
函數,創建了一個UDP
的socket
,並綁定到COAP_DEFAULT_PORT
(5684)端口上面上面。這個socket
賦值給g_serverFd
,在下面還會使用。
CoapCreateUdpServer()
函數的實現基本上都是用socket
族的標准接口實現,首先是socket()
創建一個socket
,然后用bind()
綁定到指定的IP+PORT
。 -
CoapInitWifiEvent()
CoapInitWifiEvent()
函數大致分為兩個部分:
CreateMsgQue()
創建一個消息隊列,使用RegisterWifiEvent()
向wifi_lite
注冊事件回調函數。- 創建wifi消息隊列的處理線程(任務)
CoapWifiEventThread()
- CreateCoapListenThread()
創建了一個線程CoapReadHandle
,用於處理COAP_DEFAULT_PORT
端口上的UDP socket
的數據(也就是基於COAP協議的discover廣播消息)
F.調用CoapWriteMsgQueue()觸發獲取wifi的IP地址,並啟動總線
這個函數就是向wifi的消息隊列寫入一個消息,強制觸發消息回調函數(WifiEventTrigger()
)的執行。流程圖如下:
G.向COAP中注冊設備信息
這個函數主要完成g_localDeviceInfo數據結構的初始化
整個InitService的流程圖如下:
⑤ 將Publish信息加入到Module列表
AddPublishModule()
函數,將把moduleName
和info
(PublishInfo結構)中的內容加入到g_publishModule
全局數組中
⑥ 注冊COAP服務
⑦ 回調發布成功
調用PublishCallback()
執行cb
中的發布成功的回調函數。例如在分布式調度子系統中,使用這個回調函數可以繼續進行session server
的創建。
4.2.2 數據傳輸
被發現端發布服務后,軟總線回調發布成功的函數中,被發現端需要使用CreateSessionServer()
來創建會話的服務器等待發現端的連接創建會話。
數據傳輸部分的代碼位於foundation/communication/services/softbus_lite/trans_service
目錄中。
整體流程圖如下:
① CreateSessionServer()
CreateSessionServer()
函數的實現就在tcp_session_manager.c
中。函數的代碼實現如下:
SoftBusCheckPermission()
就是檢查權限。GetTcpMgrLock()
和ReleaseTcpMgrLock()
就是通過g_sessionManagerLock
這個Mutex
進行互斥操作,防止同一時間多個模塊同時在創建Session Server
。
真正的業務邏輯在CreateSessionServerInner()
函數中:
上面SessionListenerMap結構中,最重要的是listener成員,這個成員的結構如下:
其中:
- onSessionOpened:是在會話創建時被回調的函數
- onSessionClosed:是在會話結束時被回調的函數
- onBytesReceived:是會話的數據到達的回調函數,注冊的模塊可以通過這個函數接收會話的報文,按照自己的格式進行解析,並執行會話要求的動作。例如:在分布式調度模塊中,接收的數據解析后,可能是START_FA的命令。
② 處理所有的會話數據接收
在StartBus()函數會調用StartSession()函數創建基於TCP的socket的會話管理服務。這個服務的任務線程是SelectSessionLoop()。
這個線程的主體結構就是一個while
循環,這個循環只有在maxFd
為負值的時候才會退出循環。
maxFd
是InitSelectList()
函數的返回值,只有出錯的情況才會返回負值。
InitSelectList()
函數就是根據g_sessionMgr->sessionMap_[]
數組以及g_sessionMgr->listenFd
構造一個fd_set
,這個fd_set
將在下面的select
中使用。
- g_sessionMgr->listenFd:就是在
StartBus()
流程里面創建的本地會話服務器的基於TCP
的socket
。這個socket
負責會話的連接請求,所有其他智慧屏設備都是向這個輕量設備側的socket
發起連接請求的。 - g_sessionMgr->sessionMap_[]:這個里面放的是
TcpSession
類型的數組。當連接請求被接受后,本地會生成一個新的socket
用於數據的傳輸,這個socket
就被放在TcpSession
類型里面。所以g_sessionMgr->sessionMap_[]
里面放的是所有的已經建立連接的會話的socket
。
因此,上面的InitSelectList()
函數得到的是 ListenFd
+ 所有已經建立連接的socket這樣一個fd_set
。那么select()
函數的功能就是等待這些所有socket上是否有數據發生,如果有數據發生就會往下走去處理數據。
③ 處理數據
處理數據是在ProcessData()
函數里面,代碼如下:
這個函數傳入的參數 tsm
就是 g_sessionMgr
, rfds
就是前面 InitSelectList()
函數得到的 ListenFd + 所有已經建立連接的socket這樣一個fd_set。
FD_ISSET()
語句就是判斷rfds
中的listenFd
是否被置位了,也就是監聽連接的socket是否有數據了。
如果有數據就用 ProcessConnection()
函數去處理連接請求。這個函數里面會在accept
后新生成一個socket
,然后構造一個TcpSession
把新生成用於數據傳輸的socket
放在TcpSession
里面,然后再把TCPSession
放在g_sessionMgr->sessionMap_[]
數組里面。當下一次執行InitSelectList()
的時候,fd_set
中就會包含這個新建立連接的socket
了。
最后就是ProcessSessionData()函數的調用。這個函數就是處理已經建立連接的那些會話的數據。
OnProcessDataAvailable()函數的實現如下:
GetSessionListenerByName()
函數就是通過sessionName
從g_sessionMgr->serverListenerMap[]
數組中找到匹配的SessionListenerMap
對象。下面調用TcpSessionRecv()
函數進行數據的接收和解碼。- 當數據接收完成后,就通過
SessionListenerMap
對象中的onBytesReceived()
回調函數回調給軟總線的使用者。
五.參考文檔
[1] 深圳市奧思網絡科技有限公司.源碼獲取[EB/OL].https://gitee.com/openharmony/docs/blob/master/get-code/源碼獲取.md#section1186691118430,2020-12-12.
[2] 深圳市奧思網絡科技有限公司. 分布式通信子系統[EB/OL]. https://gitee.com/openharmony/docs/blob/master/readme/分布式通信子系統README.md,2020-12-12.
[3] 天一涯.GCC 編譯使用動態鏈接庫和靜態鏈接庫的方法[EB/OL].https://developer.aliyun.com/article/12223,2015-06-04.
[4] https://img1.wsimg.com/blobby/go/bc2bf02b-5e6a-441e-b955-a49c2535530a/downloads/HarmonyOS入門文檔_鴻蒙學堂-0001.pdf?ver=1611125617517
[5] https://blog.csdn.net/guoguo527/article/details/109014004
[6] https://blog.csdn.net/weixin_47070198/article/details/109842610
[7] https://blog.csdn.net/weixin_47070198/article/details/110066180