软总线发现机制深挖:设备怎么找到彼此
两台鸿蒙设备从"素不相识"到"互相发现",中间发生了什么?本文带你从应用层一路追到蓝牙芯片,看清发现的每一步。
不是概览,是源码级的深挖——但尽量讲得有意思。
为什么需要"发现"?
分布式软总线的三大能力是发现、组网、传输。发现是第一条链,没有发现就没有后续一切。
但"发现"这件事比想象中复杂。设备之间的通信方式五花八门——Wi-Fi、蓝牙、BLE、USB、NFC——每种方式的发现机制完全不同。如果让业务层自己去适配每种链路的发现 API,代码量爆炸。
软总线发现模块要解决的问题就是:屏蔽链路差异,提供统一的"发布-订阅"发现能力。 业务只管说"我要找有 XX 能力的设备",至于底层走 BLE 还是 Wi-Fi,不用管。
四层架构:从应用到芯片的发现之旅
发现模块内部是四层架构,数据从上往下走是发布,从下往上走是发现:
┌─────────────────────────────────────────────────┐
│ 应用层 │
│ PublishLNN() / RefreshLNN() │
└────────────────────┬────────────────────────────┘
│ 策略模式:按媒介分发
▼
┌─────────────────────────────────────────────────┐
│ L1: Discovery Manager │
│ 发布/订阅生命周期 · 四媒介调度 · 能力位图分发 │
│ g_publishInfoList / g_discoveryInfoList │
│ g_capabilityList[bitmap] │
└────────────────────┬────────────────────────────┘
│ 函数指针接口
▼
┌─────────────────────────────────────────────────┐
│ L2: 媒介实现层 │
│ BLE: 广播/扫描 · TLV编解码 · 消息驱动 │
│ CoAP: Wi-Fi组播 · HTTP风格请求 │
│ USB / NFC │
└────────────────────┬────────────────────────────┘
│ 适配接口
▼
┌─────────────────────────────────────────────────┐
│ L3: 硬件适配层 │
│ 广播通道池[21] · 扫描通道池[4] · 协议策略分发 │
└────────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ L4: OHOS 系统蓝牙/Wi-Fi 框架 │
│ BleStartAdvEx / BleStartScanEx / CoAP* │
└─────────────────────────────────────────────────┘
每一层职责分明:
| 层 | 干什么 | 关键文件 |
|---|---|---|
| L1 | 管理所有发布/订阅的生命周期,按媒介分发请求,按能力位图分发发现结果 | disc_manager.c |
| L2 | 各媒介的具体发现逻辑(BLE 广播/扫描、CoAP 组播等) | disc_ble.c / disc_coap.c |
| L3 | 管理硬件通道池,处理协议差异 | broadcast_mgr.c |
| L4 | 调用系统蓝牙/Wi-Fi API,和硬件打交道 | OHOS BT Framework |
四种媒介:各显神通
发现支持四种媒介,设为 AUTO 时 BLE + CoAP 同时跑,任一成功即可:
BLE — 近场发现主力
蓝牙低功耗广播+扫描。设备 A 通过 BLE 广播自己的能力信息,设备 B 扫描到广播包后解析出 A 的信息。
优点:功耗极低,不需要 Wi-Fi 网络,手机和平板都支持。
缺点:广播包大小有限(31 字节),大数据需要 GATT 连接后续传。
CoAP — 局域网快速发现
基于 Wi-Fi 的 CoAP(Constrained Application Protocol)协议。设备在局域网内组播自己的信息,其他设备收到组播包后解析。
优点:速度快,带宽大,能携带更多数据。
缺点:需要设备在同一 Wi-Fi 局域网内。
USB — 有线安全发现
USB 连接枚举发现。设备通过 USB 物理连接后,在枚举阶段交换信息。
优点:安全可靠,需要物理连接。
缺点:需要有线连接,场景受限。
NFC — 碰一碰发现
NFC 近场通信发现。两台设备靠近触碰时交换信息。
优点:极简交互,碰一下就行。
缺点:需要 NFC 硬件支持,距离极近。
AUTO 模式
medium = DM_AUTO 时,L1 会同时启动 BLE 和 CoAP 两条发现通道,哪条先成功就用哪条。这是最常用的模式——在有 Wi-Fi 的环境下走 CoAP 快速发现,没有 Wi-Fi 时退化为 BLE 发现。
发布-订阅:电台与收音机
发现模块的核心交互模式是发布-订阅,像广播电台和收音机:
发布端(电台)
设备调 PublishLNN 发布自己的能力:
PublishLNN(pkgName, publishInfo, publishCb)
publishInfo 里填什么?
| 字段 | 说明 | 常用值 |
|---|---|---|
publishId | 发布标识,停止时用 | 自定义整数 |
mode | 主动/被动 | DISCOVER_MODE_ACTIVE |
medium | 媒介 | AUTO / BLE / COAP |
freq | 频率 | LOW / MID / HIGH |
capability | 能力字符串 | "ddmpCapability" |
ranging | 是否测距 | true |
发布后,软总线在后台持续广播,直到调 StopPublishLNN 停止。
订阅端(收音机)
设备调 RefreshLNN 订阅感兴趣的能力:
RefreshLNN(pkgName, subscribeInfo, refreshCb)
subscribeInfo 里填你要找什么能力、用什么媒介、什么频率。然后等回调 OnDeviceFound 把发现的设备推给你。
能力匹配
发现是按"能力"匹配的。发布端说"我有 ddmpCapability",订阅端说"我要找 ddmpCapability",两边对上了就发现成功。
能力不止一个,软总线用能力位图来高效管理。
能力位图:位运算的高效匹配
软总线用位图(bitmap)表示设备能力,每个能力占一位:
| 位 | 能力 | 含义 |
|---|---|---|
| 2 | CASTPLUS | 投屏能力 |
| 4 | DVKIT | DV 设备能力 |
| 6 | OSD | 分布式屏幕能力 |
| 7 | SHARE | 共享能力 |
发现匹配时不用字符串比较,直接按位与:
if (publishCapability & subscribeCapabilityMask) {
// 能力匹配,上报发现结果
}
BLE 模块只关心部分能力,用 g_concernCapabilityMask = CAST | DVKIT | OSD 过滤掉不关心的。
L1 维护一个 g_capabilityList[bitmap] 数组,发现到设备后遍历所有订阅了对应能力的业务,逐一分发。这是观察者模式——发布者不关心谁订阅了,管理器负责按能力位图分发。
BLE 发现深挖:广播、扫描与 TLV
BLE 是最常用的发现媒介,也是实现最复杂的。深入看看。
广播:把信息塞进 31 字节
BLE 广播包最大只有 31 字节,要塞下设备 ID、能力信息、距离标识等,必须紧凑编码。软总线用 TLV(Type-Length-Value) 格式编码:
[T][L][V][T][L][V]...
每个字段 3 字节起步:1 字节类型 + 1 字节长度 + N 字节数据。disc_ble_utils.c 负责编码和解码。
如果 31 字节塞不下(比如设备名太长),先放关键信息,剩余的通过 GATT 连接后续获取。
扫描:过滤与解析
设备 B 在扫描时,会收到周围所有的 BLE 广播包。不是每个包都是软总线的——可能是蓝牙耳机、可能是 Beacon。所以需要过滤:
收到广播包
→ 解析 TLV
→ 检查是否有软总线标识
→ 检查能力是否匹配
→ 通过 → 上报 OnDeviceFound
→ 不通过 → 丢弃
消息驱动:Handler-Looper 模型
BLE 发现模块内部不用线程直接操作硬件,而是用 Handler-Looper 模型:
外部接口调用
→ PostMessage(PUBLISH_ACTIVE_SERVICE)
→ 消息入队
→ Looper 串行处理
→ DiscBleMsgHandler 分发到具体处理函数
共定义了 16 种消息类型(DiscBleMessage),包括发布、扫描、停止、能力更新等。
为什么不用多线程? BLE 硬件资源有限,并发操作广播通道会冲突。用 Looper 串行处理,天然避免了竞态问题。
通道池:21 个广播 + 4 个扫描
L3 适配层管理着固定大小的通道池:
- 广播通道:21 个(
g_advChannel[21]) - 扫描通道:4 个(
g_scanChannel[4])
通道池大小固定,不可动态扩展。多个业务同时发布时,从池里分配通道;停止时归还。如果池满了,新的发布请求会被拒绝。
关键参数
| 参数 | 值 | 含义 |
|---|---|---|
| 广播间隔 | 48ms | BLE 广播包发送间隔 |
| 消息超时 | 6000ms | BLE 消息处理超时 |
| 广播启停间隔 | 50ms | 启停广播之间的最小间隔 |
48ms 的广播间隔是个权衡——太快费电,太慢发现延迟高。BLE 规范建议 20ms~10240ms,48ms 偏快,优先保证发现速度。
端到端数据流:一次发现的完整旅程
发布流程(设备 A 发布能力)
应用调 PublishLNN(pkg, info)
│
▼ L1: DiscMgr
InnerPublishService()
→ AddDiscInfoToPublishList() // 加入发布列表
→ CallInterfaceByMedium(BLE) // 策略模式:按媒介分发
│
▼ L2: DiscBle
BleStartActivePublish()
→ PostMessage(PUBLISH_ACTIVE_SERVICE) // 不直接操作硬件
│
▼ Looper
DiscBleMsgHandler()
→ StartActivePublish()
→ StartAdvertiser() // 从通道池分配广播通道
│
▼ L3: BroadcastAdapter
RegisterBroadcaster()
→ StartBroadcasting()
→ SoftbusStartAdv()
│
▼ L4: OHOS BT
BleStartAdvEx() // 调系统蓝牙 API,芯片开始广播
发现流程(设备 B 扫描到 A)
OHOS BT 芯片收到广播包
│
▼ L4 → L3
WrapperScanResultCb()
→ ParseScanResult(TLV) // 解析 TLV 编码
→ BcReportScanDataCallback()
│
▼ L2: DiscBle
BleScanResultCallback()
→ ScanFilter() // 过滤非软总线广播
→ ProcessDistributePacket()
→ GetDeviceInfoFromDisAdvData() // TLV 解码出设备信息
→ RangeDevice() // 测距(如果启用)
│
▼ L1: DiscMgr
DiscOnDeviceFound()
→ 遍历 g_capabilityList[bitmap] // 按能力位图分发
│
▼ 应用层
OnDeviceFound(device) // 回调业务
从芯片收到广播包到业务回调,经过 4 层处理,每层职责清晰:L3 解析协议,L2 过滤解码,L1 分发,应用层收结果。
设计模式:发现模块的工程智慧
发现模块用了好几种经典设计模式,值得品味:
策略模式:四媒介调度
L1 用 DiscoveryFuncInterface 函数指针数组实现策略模式:
CallInterfaceByMedium(BLE) → DiscBle 的实现
CallInterfaceByMedium(COAP) → DiscCoap 的实现
CallInterfaceByMedium(USB) → DiscUsb 的实现
新增媒介只需注册一套实现,L1 调度代码不用改。
观察者模式:能力位图分发
L1 维护 g_capabilityList[bitmap],每个能力位对应一组订阅者。发现到设备后,按能力位图遍历订阅者,逐一分发。发布者不关心谁订阅了,解耦干净。
Handler-Looper:串行消息驱动
L2 BLE 模块用消息队列驱动,外部接口只 PostMessage,Looper 串行处理。天然避免了多线程竞态,BLE 硬件操作不会冲突。
通道池:固定大小资源管理
L3 用固定大小的通道池管理广播/扫描通道,避免动态分配的开销和碎片。池满了直接拒绝,简单粗暴但可靠。
引用计数:能力生命周期
L2 用 capCount[pos] 引用计数管理能力。多个业务订阅同一能力时,计数累加;取消订阅时递减;减到 0 才真正停止广播。避免了一个业务取消订阅就把广播停了,影响其他业务。
线程安全:回调不能持锁
发现模块有一条铁律:回调不能持锁。
L1 的 DiscOnDeviceFound 持有 discovery 锁来遍历订阅者列表,但在调用 OnDeviceFound 回调之前必须先解锁。为什么?因为回调里可能调管理器接口(比如查设备信息),如果持锁回调就会死锁。
L3 更谨慎——先拷贝回调函数指针到栈变量,解锁后再用栈变量调用。这样即使回调里出问题,也不影响锁的状态。
资源管理:发现模块的洁癖
发现模块对资源管理很严格,有五条清理机制:
| 机制 | 触发条件 | 清理什么 |
|---|---|---|
| 引用计数归零 | capCount 减到 0 | 停止对应能力的广播 |
| 消息超时 | 6 秒未处理 | 丢弃超时消息 |
| 进程死亡 | DiscMgrDeathCallback | 清除该进程所有发布/订阅 |
| BT 关闭 | 收到 BLE_TURN_OFF | 停止一切 BLE 操作,重置通道状态 |
| 通道归还 | 停止发布/扫描 | 通道归还通道池 |
特别是 BT 关闭处理——收到 BLE_TURN_OFF 后,所有广播通道状态重置,所有扫描停止,不残留任何状态。下次 BT 打开时干干净净从头来。
发现之后:连接模块接棒
发现到设备后,业务拿到的是 DeviceInfo(含设备 ID、名称、能力等),但要真正通信还需要建立连接。这时连接模块接棒。
连接模块支持七种连接类型:
| 类型 | 说明 |
|---|---|
| BLE | 蓝牙低功耗连接 |
| BR | 经典蓝牙连接 |
| SLE | 星闪连接 |
| TCP | TCP 连接 |
| WiFi Direct | Wi-Fi P2P 直连 |
| Proxy | 代理连接(中继转发) |
| General | 通用连接 |
连接模块有两个关键设计:
引用计数:同一对设备间多次连接自动复用,返回相同 connectionId。ConnDisconnectDevice 减引用计数,减到 0 才真正物理断开。避免了一个业务断开就把连接断了,影响其他复用同一连接的业务。
BR 频连控制:短时间内多次经典蓝牙连接会被拒绝,防止蓝牙芯片过载。本地测试时可设 isDisableBrFrequentConnectControl = true 禁用。
小结
软总线发现机制的核心设计思想:用分层架构屏蔽链路差异,用发布-订阅解耦发现双方,用能力位图高效匹配,用 Handler-Looper 串行化硬件操作,用通道池管理有限资源。
业务看到的只是 PublishLNN 和 RefreshLNN 两个接口,背后是四层架构、四种媒介、16 种消息、21 个广播通道、4 个扫描通道的精密协作。
下次当你看到两台鸿蒙设备"自动发现"彼此时,想想那个 48ms 间隔的 BLE 广播包,和它从芯片到应用层的四层旅程。
更多推荐





所有评论(0)