HarmonyOS分布式软总线研究

时间:2024-03-04 14:51:37
HarmonyOS分布式软总线研究

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技术架构如下所示。
HarmonyOS技术架构.png

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开发的应用,能够实现特定的业务功能,支持跨设备调度与分发,为用户提供一致、高效的应用体验。

技术特性

硬件互助,资源共享

多种设备之间能够实现硬件互助、资源共享,依赖的关键技术包括分布式软总线、分布式设备虚拟化、分布式数据管理、分布式任务调度等。

分布式软总线

分布式软总线是手机、平板、智能穿戴、智慧屏、车机等分布式设备的通信基座,为设备之间的互联互通提供了统一的分布式通信能力,为设备之间的无感发现和零等待传输创造了条件。开发者只需聚焦于业务逻辑的实现,无需关注组网方式与底层协议。分布式软总线示意图如下。
分布式软总线示意图.png
典型应用场景举例:

  • 智能家居场景:在制作粉蒸肉时,手机可以通过碰一碰和烤箱连接,并将自动设置粉蒸肉的制作参数,控制烤箱来制作菜肴。与此类似,料理机、油烟机、空气净化器、空调、灯、窗帘等都可以在手机端显示并通过手机控制。设备之间即连即用,无需繁琐的配置。
  • 多屏联动课堂:老师通过智慧屏授课,与学生开展互动,营造课堂氛围;学生通过手机完成课程学习和随堂问答。统一、全连接的逻辑网络确保了传输通道的高带宽、低时延、高可靠。

分布式设备虚拟化

分布式设备虚拟化平台可以实现不同设备的资源融合、设备管理、数据处理,多种设备共同形成一个超级虚拟终端。针对不同类型的任务,为用户匹配并选择能力合适的执行硬件,让业务连续地在不同设备间流转,充分发挥不同设备的能力优势,如显示能力、摄像能力、音频能力、交互能力以及传感器能力等。分布式设备虚拟化示意图如下。
分布式设备虚拟化示意图.png
典型应用场景举例:

  • 视频通话场景:在做家务时接听视频电话,可以将手机与智慧屏连接,并将智慧屏的屏幕、摄像头与音箱虚拟化为本地资源,替代手机自身的屏幕、摄像头、听筒与扬声器,实现一边做家务、一边通过智慧屏和音箱来视频通话。
  • 游戏场景:在智慧屏上玩游戏时,可以将手机虚拟化为遥控器,借助手机的重力传感器、加速度传感器、触控能力,为玩家提供更便捷、更流畅的游戏体验。

分布式数据管理

分布式数据管理基于分布式软总线的能力,实现应用程序数据和用户数据的分布式管理。用户数据不再与单一物理设备绑定,业务逻辑与数据存储分离,跨设备的数据处理如同本地数据处理一样方便快捷,让开发者能够轻松实现全场景、多设备下的数据存储、共享和访问,为打造一致、流畅的用户体验创造了基础条件。分布式数据管理示意图如下。
分布式数据管理示意图.png
典型应用场景举例:

  • 协同办公场景:将手机上的文档投屏到智慧屏,在智慧屏上对文档执行翻页、缩放、涂鸦等操作,文档的最新状态可以在手机上同步显示。
  • 家庭出游场景:一家人出游时,妈妈用手机拍了很多照片。通过家庭照片共享,爸爸可以在自己的手机上浏览、收藏和保存这些照片,家中的爷爷奶奶也可以通过智慧屏浏览这些照片。

分布式任务调度

分布式任务调度基于分布式软总线、分布式数据管理、分布式Profile等技术特性,构建统一的分布式服务管理(发现、同步、注册、调用)机制,支持对跨设备的应用进行远程启动、远程调用、远程连接以及迁移等操作,能够根据不同设备的能力、位置、业务运行状态、资源使用情况,以及用户的习惯和意图,选择合适的设备运行分布式任务。

下图以应用迁移为例,简要地展示了分布式任务调度能力。
分布式任务调度示意图.png
典型应用场景举例:

  • 导航场景:如果用户驾车出行,上车前,在手机上规划好导航路线;上车后,导航自动迁移到车机和车载音箱;下车后,导航自动迁移回手机。如果用户骑车出行,在手机上规划好导航路线,骑行时手表可以接续导航。
  • 外卖场景:在手机上点外卖后,可以将订单信息迁移到手表上,随时查看外卖的配送状态。

一次开发,多端部署

HarmonyOS提供了用户程序框架、Ability框架以及UI框架,支持应用开发过程中多终端的业务逻辑和界面逻辑进行复用,能够实现应用的一次开发、多端部署,提升了跨设备应用的开发效率。一次开发、多端部署示意图如下。
一次开发、多端部署示意图.png
其中,UI框架支持Java和JS两种开发语言,并提供了丰富的多态控件,可以在手机、平板、智能穿戴、智慧屏、车机上显示不同的UI效果。采用业界主流设计方式,提供多种响应式布局方案,支持栅格化布局,满足不同屏幕的界面适配能力。

统一OS,弹性部署

HarmonyOS通过组件化和小型化等设计方法,支持多种终端设备按需弹性部署,能够适配不同类别的硬件资源和功能需求。支撑通过编译链关系去自动生成组件化的依赖关系,形成组件树依赖图,支撑产品系统的便捷开发,降低硬件设备的开发门槛。

  • 支持各组件的选择(组件可有可无): 根据硬件的形态和需求,可以选择所需的组件。
  • 支持组件内功能集的配置(组件可大可小): 根据硬件的资源情况和功能需求,可以选择配置组件中的功能集。例如,选择配置图形框架组件中的部分控件。
  • 支持组件间依赖的关联(平台可大可小): 根据编译链关系,可以自动生成组件化的依赖关系。例如,选择图形框架组件,将会自动选择依赖的图形引擎组件等。

系统安全

在搭载HarmonyOS的分布式终端上,可以保证“正确的人,通过正确的设备,正确地使用数据”。

  • 通过“分布式多端协同身份认证”来保证“正确的人”。 在分布式终端场景下,“正确的人”指通过身份认证的数据访问者和业务操作者。“正确的人”是确保用户数据不被非法访问、用户隐私不泄露的前提条件。
  • 通过“在分布式终端上构筑可信运行环境”来保证“正确的设备”。 在分布式终端场景下,只有保证用户使用的设备是安全可靠的,才能保证用户数据在虚拟终端上得到有效保护,避免用户隐私泄露。
  • 通过“分布式数据在跨终端流动的过程中,对数据进行分类分级管理”来保证“正确地使用数据”。 在分布式终端场景下,需要确保用户能够正确地使用数据。HarmonyOS围绕数据的生成、存储、使用、传输以及销毁过程进行全生命周期的保护,从而保证个人数据与隐私、以及系统的机密数据(如密钥)不泄漏。

分布式软总线模块详述

什么是总线

总线英文名叫Bus,就是公共汽车的意思,正如公共汽车作为运载人的一种载具一样,总线是作为信号传递的统一通道。它是一个非常广泛的概念,在传统计算机硬件体系中应用的非常广泛。

总线是一种内部结构,它是cpu、内存、输入、输出设备传递信息的公用通道,主机的各个部件通过总线相连接,外部设备通过相应的接口电路再与总线相连接,从而形成了计算机硬件系统。

在计算机系统中,各个部件之间传送信息的公共通路叫总线,微型计算机是以总线结构来连接各个功能部件的。按照计算机所传输的信息种类,计算机的总线可以划分为数据总线、地址总线和控制总线,分别用来传输数据、数据地址和控制信号。

传统总线的典型特征:

  • 即插即用
  • 高带宽
  • 低时延
  • 高可靠
  • 标准性

经典总线.png

什么是分布式软总线

分布式软总线[2]技术是基于华为多年的通信技术积累,参考计算机硬件总线,在1+8+N设备间搭建一条“无形”的总线,具备自发现、自组网、高带宽低时延的特点。

1+8+N:
1指的是手机
8代表车机、音箱、耳机、手表/手环、平板、大屏、PC、AR/VR
N泛指其他IOT设备

分布式软总线其实指的是一种多类型网络通信协议,它可以把所有鸿蒙设备连在一起,通过该协议进行通信。这里华为把它抽象成一根类似物理上的总线,就好像I2C总线,在SDA、SCL两根线上可以挂载许多设备,设备可以通过I2C总线进行互相通信。

传统开发跨端应用,开发者们常常面临以下挑战:

  1. 跨端操作需要每次重复建立连接
  2. 近场通信方式(蓝牙,WiFi,UWB等)必须感知才能实现连接
  3. 无线环境必须建立标准的用于服务器的协议栈
  4. 不同物理层无法实现统一的开发体验

针对这些开发者的开发之痛,分布式软总线的技术带来全新解决思路。它采用极简通信协议技术,包括发现&连接、组网(多跳自组网、多协议混合组网)、传输(极简传输协议:多元化协议与算法、智能感知与决策),以及开发者如何基于分布式软总线进行“三步走”极简开发。
分布式软总线为设备之间的互联互通提供了统一的分布式通信能力,为设备之间的无感发现和零等待传输创造了条件。开发者只需聚焦于业务逻辑的实现,无需关注组网方式与底层协议。

分布式总线.png
全场景设备间可以基于软总线完成设备虚拟化、跨设备服务调用、多屏协同、文件分享等分布式业务。鸿蒙分布式软总线致力于实现近场设备间统一的分布式通信能力,提供不区分链路的设备发现和传输接口,具备快速发现并连接设备,高效分发任务和传输数据。作为多终端设备的统一基座,是鸿蒙架构中的底层技术,是鸿蒙的大动脉,其总的目标是实现设备间无感发现,零等待传输。对开发者而言,无需关注组网方式与底层协议。

分布式软总线的典型特征:

  • 自动发现/即连即用
  • 高带宽
  • 低时延
  • 高可靠
  • 开放/标准

分布式软总线功能和原理

通过协议货架和软硬协同层屏蔽各种设备的协议差别,总线中枢模块负责解析命令完成设备间发现和连接,通过任务和数据两条总线实现设备间文件传输、消息传输等功能。

分布式总线的总体目标是实现设备间无感发现,零等待传输。实现这个目标需要解决三个问题:

  1. 设备间如何发现和连接?
  2. 多设备互联后如何组网?
  3. 多设备多协议间如何实现传输?

设备间自发现&连接

传统的设备发现是手动的,需要人干预,以生活中常见的一个例子讲解:
比如手机上有很多照片需要传到个人PC上,我们可以采用蓝牙传输,首先要打开手机和PC的蓝牙发现功能,手机或者PC点击搜索设备,然后互相配对授权即可连接上,成功连上后就可以肆无忌惮的发送照片啦。在分享照片这个场景中有很多人为的动作:开启蓝牙发现功能、搜索设备、配对授权,这确实有点麻烦,耗费了很多时间,可能会降低分享的意愿。

分布式软总线提出自动发现设备,实现用户零等待的自发现体验,附近同账号的设备自动发现无需等待,自动安全连接。

IoT设备分为发现端和被发现端。发现端一般为请求使用服务的设备或称为主控设备,常指智慧屏设备(如手机、平板等)。被发现端为发布服务的设备,指轻量设备(如AI音箱、智能家居、智能穿戴等设备)。目前软总线的设备互联,需保证发现端和被发现端处于同一个局域网内。

yktl1x.jpg

发现端设备,发起discover请求后,使用coap协议在局域网内发送广播。报文如下:

被发现端设备使用PublishService接口发布服务,接收端收到广播后,发送coap协议单播给发现端。报文格式如下:

发现端设备收到报文会更新设备信息。
发现的流程图如下:

多设备互联、组网

基于网络互联、交互的系统,开发者往往需要适配不同网络协议和标准规范。而在鸿蒙系统设定的分布式开发模式中,无需关心网络协议的差异及组网方式,业务开发与设备组网解耦,仅需监听设备上下线,开发成本大大降低。

分布式软总线提出了异构网络组网,自动构建一个逻辑全连接网络,以解决设备间不同协议交互的问题。设备上线后会向网络层注册,同时网络层会与设备建立通道连接,实时检测设备的变换。网络层负责管理设备的上线、下线变换,设备间可以监听自己感兴趣的设备,设备上线后可以立即与其建立连接,实现零等待体验。

yktj81.png

设备上线后会向网络层注册,同时网络层会与设备建立通道连接,实时检测设备的变换。网络层负责管理设备的上线下线变换,设备间可以监听自己感兴趣的设备,设备上线后可以立即与其建立连接,实现零等待体验。

软总线可以自动构建一个逻辑全连接网络,用户或者业务开发者无需关心组网方式与物理协议。对于软件开发者来说软总线异构组网可以大大降低其开发成本。

在传统开发模式中开发者需要适配不同网络协议和标准规范。

在HarmonyOS分布式开发模式中开发不再需要关心网络协议差异,业务开发与设备组网解耦,业务仅需监听设备上下线,开发成本大大降低。

多设备间数据传输

传统协议的传输速率差异非常大,时延也难以得到保证。
软总线传输要实现的目标:

  • 高带宽(High Speed)
  • 低时延(Low Latency)
  • 高可靠(High Reliability)

软总线要实现的这三大目标的尖刀武器是:极简协议。

yktXCR.png

将中间的四层协议栈精简为一层提升有效载荷,有效传输带宽提升20%。
极简协议在传统网络协议的基础上进行增强:

  • 流式传输:基于UDP实现数据的保序和可靠传输;
  • 双轮驱动:颠覆传统TCP每包确认机制;
  • 不惧网损:摒弃传统滑动窗口机制,丢包快速恢复,避免阻塞;
  • 不惧抖动:智能感知网络变化,自适应流量控制和拥塞控制;

分布式软总线源码分析

源码目录结构

HarmonyOS[3]代码已在gitee开源,其中分布式软中线代码仓库地址如下:

communication_interfaces_kits_softbuskit_lite

communication_services_softbus_lite

顾名思义,分别对应它的接口和实现;而communication_services_softbus_lite源码结构中,又分为authmanagerdiscoverytrans_service和为兼容系统差别而生的os_adapter四大目录。

ykafCd.png

authmanager
提供设备认证机制和设备知识库管理,当发现有请求时,调用ProcessDataEvent函数,收包,检验包头,根据数据包的类型确定不同的处理方式。类型主要包括以下三种:

  1. MODULE_AUTH_SDK 加密数据类型
  2. MODULE_TRUST_ENGINE 可信类型,直接进行数据传输
  3. 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,该目录文件结构如下:

yk4wjK.png

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()函数实现了如下的功能:

  1. 初始化了g_deviceInfo结构体,包括:deviceName,deviceId,deviceIp。

  2. 注册wifi_lite的event监控事件,当wifi链路发生变化的情况下(例如:设备wifi连接成功),获取当前设备wifi的IP地址,并放入到g_deviceInfo->deviceIp中,为下面TCP/IP协议栈的初始化做准备。

  3. 初始化UDP协议socket绑定在COAP_DEFAULT_PORT端口上,监听COAP的discover消息,并进行处理。

  4. 根据deviceIp初始化TCP协议socket,并且启动一个监听任务,处理连接请求,并进行auth的校验。

  5. 根据deviceIp初始化TCP协议socket,并且启动一个监听任务,处理session的会话请求。

设备发现整体流程总结:

  1. 设备发现部分代码(主要是轻量设备侧)主入口函数为PublishService()函数。

  2. PublishService()函数会检查软总线的服务是否已经初始化过,如果没有则会初始化软总线的所有服务,包括:a、基于UDP的COAP协议discover发现服务。b、wifi设备状态监听服务。c、基于TCP的认证服务。d、基于TCP的session会话管理服务。

  3. PublishService()然后会把模块的信息加入g_publishModule全局数组中。

  4. 回调模块的发布成功回调函数。

总体时序图:
ykLTVP.png

编译测试

环境搭建

在进行源码编译之前,先进行编译环境的搭建[4]

  1. 安装nodejs和npm
    sudo apt-get install nodejs
    sudo apt install libssl1.0-dev nodejs-dev node-gyp npm

  2. 通过Node.js自带的npm安装hpm-cli命令行工具。执行以下命令:
    npm install -g @ohos/hpm-cli

  3. 安装完成后执行如下命令,显示hpm版本,即安装成功。
    hpm -V 或 hpm --version

  4. 下载HarmonyOS代码

  5. 安装开发依赖的组件
    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等工具),有了这些开发态的组件,就可以进行常规的源码组件的开发了。

  6. 将linux shell改为bash:

    ls -l $(which sh)
    # 如果指向的不是bash,则按以下方式修改:
    # 方法一:执行以下命令,然后选择no
    dpkg-reconfigure dash
    # 方法二:先删除sh,再重新创建软连接
    rm -f /bin/sh
    ln -s bash /bin/sh
    
  7. 确认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

执行makefile文件编译代码,
make
yAECoF.md.png

测试

新建测试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
效果如下:
11.png


  1. HarmonyOS概述官方文档 ↩︎

  2. 分布式软总线功能和特性 ↩︎

  3. HarmonyOS之51CTO官方战略合作社区系列博文 ↩︎

  4. 华为gitee开发者文档 ↩︎