分布式软总线组件拆包:从发现到传输的源码解析
2026/9/23 22:44:03 网站建设 项目流程

简介:这份资源是面向 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.csoftbus_conn_ble_manager.csoftbus_conn_br_manager.cdisc_ble.cp2p_v1_processor.cfillp_conn.cnstackx_dfile_session.cnstackx_file_manager.cclient_trans_proxy_file_manager.clnn_distributed_net_ledger.c。它适合正在做 OpenHarmony 底层组件适配、或者想搞清楚近场设备互联到底怎么落地的人。下面按“发现怎么触发、组网怎么建、传输怎么选通道”这条线,把每个模块的职责和可复现的操作拆开讲。

2. 发现与组网:disc_ble.clnn_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。每个状态迁移都有对应的超时和重试。

关键函数是LnnNetBuilderOnDeviceFoundLnnNetBuilderOnConnectResult。前者把发现到的设备信息写入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里必须带deviceIddevTypeconnAddr三个字段,缺一个账本就写不进去。LnnNotifyTopologyChanged是同步调用,如果上层业务处理慢会阻塞组网线程,常见做法是丢到独立任务队列里异步通知。

2.3 设备账本:lnn_distributed_net_ledger.c的读写与一致性

lnn_distributed_net_ledger.c本质是一个带锁的哈希表,key 是deviceId,value 是设备完整信息。它提供LnnAddDeviceToLedgerLnnRemoveDeviceFromLedgerLnnGetDeviceInfo三个主要接口。读写都要加锁,因为发现线程和传输线程会并发访问。

踩坑最多的地方是设备下线时账本没清干净,导致传输模块拿到一个已经断开的设备地址去发数据,直接超时。正确做法是在LnnNetBuilderOnDeviceOffline里先调LnnRemoveDeviceFromLedger,再通知拓扑变化。

3. 连接管理:BLE 和 BR 两条链路的差异化处理

3.1softbus_conn_ble_manager.c:BLE 连接的生命周期

BLE 连接管理负责 GATT 连接的建立、MTU 协商、连接参数更新和断开重连。核心结构是BleConnInfo,里面存了connIdmtustatecallback。连接建立后第一件事是协商 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 链路走的是 RFCOMM,softbus_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,什么时候用 BR

BLE 适合低功耗、小数据量场景,比如设备发现和信令交互;BR 适合大数据量、对延迟不敏感的场景,比如文件传输。实际组网中常见做法是:发现阶段用 BLE 广播,组网信令走 BLE GATT,文件传输自动切到 BR 或 WiFi P2P。

切换逻辑在lnn_net_builder.c里根据数据类型判断:消息和字节走 BLE,流和文件走 BR 或 P2P。如果 BR 不可用,降级到 BLE 分片传输,但速度会明显下降。

4. 传输通道:P2P、FillP 和文件传输的落地细节

4.1p2p_v1_processor.c:WiFi P2P 的协商与建连

P2P 传输的前提是 WiFi P2P 组网成功。p2p_v1_processor.c处理 GO(Group 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.cnstackx_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 > BLE,BLE 最慢但最稳。

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.csoftbus_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设 200ms,LINK_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 均值打出来,再决定传输策略。这个习惯帮我省了很多“明明连上了但传不动”的排查时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询