ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

分布式软总线组件拆包:从发现到传输的源码解析

2026/9/23 22:44:07 拓冰建站 浏览量
分布式软总线组件拆包:从发现到传输的源码解析 简介这份资源是面向 OpenHarmony 底层开发者的分布式软总线组件源码包聚焦近场设备间统一通信管理能力的实现。它针对现实中 WiFi、蓝牙等多种通信方式差异大、链路融合共享与冲突难以处理的痛点提供不区分链路的设备发现连接、组网与传输能力涵盖基于 WiFi、蓝牙的设备发现连接、统一组网与拓扑管理以及支持消息、字节、流、文件的数据传输通道适合具备一定系统与网络编程基础的中高级开发者研读。资源包共约 2000 个文件以 702 个 h 头文件、531 个 c 与 433 个 cpp 源文件为主体辅以 137 个 gn、24 个 gni 构建脚本及 91 个 xml、70 个 init 配置整体约 3.7MB结构完整。内容涉及组网构建、连接管理、文件传输代理、分布式网络账本、P2P 与 FillP 连接、DFile 会话等模块可帮助读者梳理软总线发现、组网、传输的代码脉络与排错思路。目前已有 383 人学习下载。1. 分布式软总线组件拆包从发现到传输这套源码能跑通什么多设备通信最让人头疼的不是“连不上”而是“同一种业务在 WiFi 下能跑、换蓝牙就翻车”。分布式软总线要解决的就是这件事把 WiFi、蓝牙这些差异极大的物理链路抽象成统一接口让上层业务不关心底层走的是哪条路。这次拆的这套组件源码覆盖了软总线最核心的三块——发现、组网、传输具体文件包括lnn_net_builder.c、softbus_conn_ble_manager.c、softbus_conn_br_manager.c、disc_ble.c、p2p_v1_processor.c、fillp_conn.c、nstackx_dfile_session.c、nstackx_file_manager.c、client_trans_proxy_file_manager.c、lnn_distributed_net_ledger.c。它适合正在做 OpenHarmony 底层组件适配、或者想搞清楚近场设备互联到底怎么落地的人。下面按“发现怎么触发、组网怎么建、传输怎么选通道”这条线把每个模块的职责和可复现的操作拆开讲。2. 发现与组网disc_ble.c和lnn_net_builder.c怎么串起来2.1 蓝牙发现入口disc_ble.c的广播与扫描逻辑disc_ble.c是蓝牙发现的核心负责 BLE 广播包的组装、发送以及扫描回调的分发。它不直接处理连接只负责“让对方知道自己存在”和“发现别人”。常见做法是设备上线后先启动广播广播内容里带设备标识和能力位同时启动扫描收到广播后回调给上层做设备匹配。下面这段是广播启动的典型调用链参数需要按实际设备能力调整// disc_ble.c 中启动 BLE 广播的核心逻辑 static int StartBleAdv(void) { BleAdvParam param {0}; param.advType BLE_ADV_TYPE_CONNECTABLE; // 可连接广播组网需要 param.advInterval BLE_ADV_INTERVAL_128MS; // 广播间隔太短耗电太长发现慢 param.txPower BLE_TX_POWER_HIGH; // 发射功率影响发现距离 param.channelMap BLE_ADV_CHANNEL_ALL; // 37/38/39 三通道全开 int ret BleStartAdv(param); if (ret ! SOFTBUS_OK) { SOFTBUS_LOGE(StartBleAdv failed, ret%d, ret); return ret; } return SOFTBUS_OK; }逻辑说明advType选可连接广播是因为后续组网要基于这个广播建立 GATT 连接advInterval设 128ms 是平衡功耗和发现速度的常用值如果设备是常供电的可以降到 32ms 加快发现。channelMap全开是为了避免单一通道干扰导致发现失败。参数改错最直接的表现是广播发出去了但对方扫不到或者扫到了连不上。扫描侧的回调在disc_ble.c里通过BleScanCallback注册收到广播后先做设备类型过滤再交给lnn_net_builder.c做设备信息入库。2.2 组网状态机lnn_net_builder.c如何管理设备上线与拓扑lnn_net_builder.c是组网的大脑维护了一个设备状态机发现到设备后先标记为DEVICE_STATE_DISCOVERED发起连接后变DEVICE_STATE_CONNECTING连接成功且认证通过后变DEVICE_STATE_ONLINE。每个状态迁移都有对应的超时和重试。关键函数是LnnNetBuilderOnDeviceFound和LnnNetBuilderOnConnectResult。前者把发现到的设备信息写入lnn_distributed_net_ledger.c管理的设备账本后者根据连接结果更新状态并触发拓扑刷新。// lnn_net_builder.c 中设备上线后的拓扑更新 int LnnNetBuilderOnDeviceOnline(const DeviceInfo *info) { if (info NULL) { return SOFTBUS_INVALID_PARAM; } // 写入设备账本后续传输模块靠这个查设备 int ret LnnAddDeviceToLedger(info); if (ret ! SOFTBUS_OK) { SOFTBUS_LOGE(add to ledger failed); return ret; } // 通知拓扑变化触发上层业务刷新设备列表 LnnNotifyTopologyChanged(); return SOFTBUS_OK; }参数说明DeviceInfo里必须带deviceId、devType、connAddr三个字段缺一个账本就写不进去。LnnNotifyTopologyChanged是同步调用如果上层业务处理慢会阻塞组网线程常见做法是丢到独立任务队列里异步通知。2.3 设备账本lnn_distributed_net_ledger.c的读写与一致性lnn_distributed_net_ledger.c本质是一个带锁的哈希表key 是deviceIdvalue 是设备完整信息。它提供LnnAddDeviceToLedger、LnnRemoveDeviceFromLedger、LnnGetDeviceInfo三个主要接口。读写都要加锁因为发现线程和传输线程会并发访问。踩坑最多的地方是设备下线时账本没清干净导致传输模块拿到一个已经断开的设备地址去发数据直接超时。正确做法是在LnnNetBuilderOnDeviceOffline里先调LnnRemoveDeviceFromLedger再通知拓扑变化。3. 连接管理BLE 和 BR 两条链路的差异化处理3.1softbus_conn_ble_manager.cBLE 连接的生命周期BLE 连接管理负责 GATT 连接的建立、MTU 协商、连接参数更新和断开重连。核心结构是BleConnInfo里面存了connId、mtu、state、callback。连接建立后第一件事是协商 MTU默认 23 字节协商到 512 字节才能跑文件传输。// softbus_conn_ble_manager.c 中 MTU 协商后的回调 static void OnMtuChanged(uint32_t connId, uint16_t mtu) { BleConnInfo *info GetBleConnInfo(connId); if (info NULL) { return; } info-mtu mtu; // MTU 小于 128 时传输效率极低记录告警 if (mtu BLE_MIN_EFFICIENT_MTU) { SOFTBUS_LOGW(ble mtu too small: %u, mtu); } // 通知传输层可以开始发数据 NotifyConnReady(connId, mtu); }参数说明BLE_MIN_EFFICIENT_MTU一般设 128低于这个值文件传输会频繁分片速度掉得厉害。MTU 协商失败时mtu保持默认 23这时候传输层应该降级到小消息模式而不是硬发大包。3.2softbus_conn_br_manager.c经典蓝牙的 RFCOMM 通道BR 链路走的是 RFCOMMsoftbus_conn_br_manager.c管理 socket 的创建、连接和读写。和 BLE 不同BR 没有 MTU 协商但需要处理 RFCOMM 通道号的分配。常见做法是服务端监听固定通道号客户端连接时先做 SDP 查询拿到通道号再连。// softbus_conn_br_manager.c 中建立 RFCOMM 连接 static int BrConnect(const BrAddr *addr) { int sock socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); if (sock 0) { return SOFTBUS_ERR; } struct sockaddr_rc rcAddr {0}; rcAddr.rc_family AF_BLUETOOTH; rcAddr.rc_bdaddr addr-bdAddr; rcAddr.rc_channel GetRfcommChannel(addr); // 从 SDP 缓存拿通道号 int ret connect(sock, (struct sockaddr *)rcAddr, sizeof(rcAddr)); if (ret 0) { close(sock); return SOFTBUS_ERR; } return sock; }参数说明rc_channel不能写死不同设备的 RFCOMM 通道号可能不同必须通过 SDP 查询获取。GetRfcommChannel内部有缓存第一次查完存起来后续直接用。如果连接返回ECONNREFUSED大概率是通道号不对或者对端没监听。3.3 两条链路的选型与切换什么时候用 BLE什么时候用 BRBLE 适合低功耗、小数据量场景比如设备发现和信令交互BR 适合大数据量、对延迟不敏感的场景比如文件传输。实际组网中常见做法是发现阶段用 BLE 广播组网信令走 BLE GATT文件传输自动切到 BR 或 WiFi P2P。切换逻辑在lnn_net_builder.c里根据数据类型判断消息和字节走 BLE流和文件走 BR 或 P2P。如果 BR 不可用降级到 BLE 分片传输但速度会明显下降。4. 传输通道P2P、FillP 和文件传输的落地细节4.1p2p_v1_processor.cWiFi P2P 的协商与建连P2P 传输的前提是 WiFi P2P 组网成功。p2p_v1_processor.c处理 GOGroup Owner协商、DHCP 分配和 socket 建立。协商阶段双方交换 GO Intent 值值大的当 GO值相同则随机。GO 确定后启动 DHCP 服务端客户端拿 IP 后建 socket。// p2p_v1_processor.c 中处理 GO 协商结果 static void OnGoNegotiationResult(int result, const char *goMac) { if (result P2P_GO_NEG_SUCCESS) { // 协商成功启动 DHCP 或请求 IP if (IsGo()) { StartDhcpServer(); } else { RequestDhcpIp(); } // 通知传输层 P2P 通道就绪 NotifyP2pReady(goMac); } else { SOFTBUS_LOGE(go negotiation failed: %d, result); // 协商失败降级到 BR FallbackToBr(); } }参数说明goMac是 GO 的 MAC 地址后续 socket 连接要用。FallbackToBr是降级逻辑P2P 协商失败不能直接报错要自动切到 BR 保证业务不中断。4.2fillp_conn.c可靠传输的拥塞控制与重传FillP 是软总线自研的可靠传输协议fillp_conn.c实现了滑动窗口、ACK 确认和超时重传。它比 TCP 轻量适合近场低延迟场景。核心参数是窗口大小和 RTO重传超时。// fillp_conn.c 中发送窗口的初始化 static void FillpInitSendWindow(FillpConn *conn) { conn-sendWindow FILLP_DEFAULT_WINDOW; // 默认 64 个包 conn-rto FILLP_MIN_RTO; // 最小 RTO 100ms conn-maxRetry FILLP_MAX_RETRY; // 最大重传 5 次 // 根据链路质量动态调整 if (conn-linkQuality LINK_QUALITY_POOR) { conn-sendWindow FILLP_MIN_WINDOW; // 链路差时缩小窗口 conn-rto FILLP_MAX_RTO; // 拉长重传超时 } }参数说明FILLP_DEFAULT_WINDOW设 64 是吞吐和内存的折中链路质量差时降到 16 避免拥塞。rto最小 100ms最大 1s根据 RTT 动态计算。重传超过maxRetry直接断连通知上层。4.3nstackx_dfile_session.c与nstackx_file_manager.c文件分片与重组文件传输走的是 dfile 协议nstackx_dfile_session.c管理会话nstackx_file_manager.c管理文件分片。大文件切成固定大小的块每块带序号接收端按序号重组。分片大小默认 64KB可根据 MTU 调整。// nstackx_file_manager.c 中文件分片逻辑 static int SplitFileToBlocks(const char *filePath, uint32_t blockSize) { int fd open(filePath, O_RDONLY); if (fd 0) { return SOFTBUS_ERR; } uint32_t index 0; uint8_t *buf malloc(blockSize); ssize_t readLen; while ((readLen read(fd, buf, blockSize)) 0) { // 每块带序号和总块数接收端靠这个重组 SendBlock(index, readLen, buf); } free(buf); close(fd); return SOFTBUS_OK; }参数说明blockSize默认 64KB如果底层 MTU 只有 512 字节分片太大会导致底层再分片效率反而低。常见做法是blockSize设为 MTU 的整数倍。index从 0 开始接收端按序写入临时文件全部收齐后 rename 成目标文件。4.4client_trans_proxy_file_manager.c代理层的文件传输调度代理层负责把上层的文件传输请求路由到正确的通道P2P/BR/BLE并管理传输优先级。多个文件同时传时按优先级排队小文件优先避免大文件阻塞信令。// client_trans_proxy_file_manager.c 中的传输调度 static int ProxySendFile(const FileRequest *req) { // 根据文件大小和当前通道负载选路 if (req-fileSize LARGE_FILE_THRESHOLD IsP2pAvailable()) { return SendViaP2p(req); } else if (IsBrAvailable()) { return SendViaBr(req); } else { return SendViaBle(req); // 最后降级到 BLE } }参数说明LARGE_FILE_THRESHOLD一般设 1MB超过走 P2P。IsP2pAvailable检查 P2P 通道是否就绪没就绪就降级。降级顺序是 P2P BR BLEBLE 最慢但最稳。5. 避坑与排查这套组件跑起来最容易翻车的五个点5.1 现象BLE 广播发出去了但对方扫不到原因广播通道被占用或者广播间隔太长。常见于多设备同时广播的场景37/38/39 三个通道如果只开了一个碰撞概率高。解决channelMap设为BLE_ADV_CHANNEL_ALLadvInterval降到 32ms 测试。如果还不行检查设备是否支持 BLE 5.0 的扩展广播老设备只支持传统广播扫描窗口要匹配。5.2 现象组网成功但传输一直超时原因lnn_distributed_net_ledger.c里的设备地址过期了。设备下线后账本没清传输模块拿到旧地址去连必然超时。解决在LnnNetBuilderOnDeviceOffline里强制调LnnRemoveDeviceFromLedger并且传输前先LnnGetDeviceInfo校验设备状态状态不是ONLINE直接返回错误。5.3 现象P2P 协商成功但 socket 连不上原因DHCP 没拿到 IP 或者防火墙拦了。P2P 组网后 GO 端要启动 DHCP 服务端客户端请求 IP这一步失败的话 socket 层根本没法通信。解决检查StartDhcpServer是否返回成功客户端RequestDhcpIp超时时间设长一点默认 5s可以调到 10s。如果 IP 拿到了还连不上检查 socket 绑定的是不是 P2P 网卡的地址。5.4 现象文件传输到 99% 卡住原因最后一个分片丢了或者 ACK 没收到。FillP 的重传机制在最后一个包上容易出问题因为发送端已经关了窗口接收端还在等。解决在nstackx_file_manager.c里加一个收尾超时超过 3s 没收到最后一块就主动请求重传。或者把maxRetry调大确保最后一个包有足够重传机会。5.5 现象BLE 和 BR 同时连接时互相干扰原因两条链路共用天线BLE 广播和 BR 数据传输抢射频资源。常见于手机同时开 BLE 和 BR 的场景。解决在softbus_conn_ble_manager.c和softbus_conn_br_manager.c之间加互斥锁BR 传输时暂停 BLE 广播传输完再恢复。或者把 BLE 广播间隔拉长减少射频占用。6. 进阶技巧用lnn_net_builder.c的状态机做链路质量探测这套组件里最容易被忽略的是lnn_net_builder.c的状态机其实可以拿来当链路质量探测器。每次状态迁移的时间戳都记下来发现到连接的时间、连接建立的时间、认证的时间这三个值能反映链路质量。我一般会在LnnNetBuilderOnConnectResult里加一段统计逻辑// 在 lnn_net_builder.c 中加链路质量统计 static void RecordLinkQuality(const DeviceInfo *info, uint64_t costMs) { // costMs 是从发起连接到连接成功的耗时 if (costMs LINK_QUALITY_GOOD_MS) { info-linkQuality LINK_QUALITY_GOOD; } else if (costMs LINK_QUALITY_FAIR_MS) { info-linkQuality LINK_QUALITY_FAIR; } else { info-linkQuality LINK_QUALITY_POOR; } // 写入账本传输模块选路时参考 LnnUpdateDeviceLinkQuality(info-deviceId, info-linkQuality); }参数说明LINK_QUALITY_GOOD_MS设 200msLINK_QUALITY_FAIR_MS设 500ms超过 500ms 算差。这个值不是固定的BLE 连接普遍比 BR 慢可以按链路类型分别设阈值。传输模块在client_trans_proxy_file_manager.c里选路时优先选LINK_QUALITY_GOOD的通道差的通道只跑信令不跑数据。验证方法很简单找两台设备一台正常距离一台拉远到 10 米外分别组网看costMs的差异。正常距离 BLE 连接大概 150ms 左右10 米外可能到 800ms 以上这时候传输就该切到 BR 或者 P2P。还有一个技巧是拿fillp_conn.c的 RTO 值反推链路质量。RTO 动态调整的过程中如果一直维持在最小值 100ms说明链路很稳如果频繁跳到 500ms 以上说明丢包严重这时候即使连接没断传输速度也会掉得厉害。我一般会在 FillP 的连接结构里加一个rtoHistory数组记录最近 10 次 RTO 值取平均作为链路质量的辅助判断。从那以后我每次调软总线传输都强制先跑一遍链路质量探测把costMs和 RTO 均值打出来再决定传输策略。这个习惯帮我省了很多“明明连上了但传不动”的排查时间。希望帮到你。本文还有配套的精品资源点击获取