一.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