从配对到传输:鸿蒙软总线通信全链路实录
两台设备配对成功后,数据是怎么从一台飞到另一台的?本文带你走完从鉴权完成到数据落地的完整旅程。
不是概览,不是缩略,是每一步都在的完整流程——但尽量讲得有意思一点。
故事背景
假设我们有两台设备:手机(设备 A)和智慧屏(设备 B)。它们已经通过 PIN 码完成了配对,互信关系建好了,ACL 表里互相是"可信设备"。
现在手机想把一张照片投到智慧屏上。这张照片要怎么过去?
答案是走软总线。但软总线不是"配对完了就能发数据"那么简单——中间还要经过发现、组网、传输三个阶段,每个阶段都有自己的故事。
第一幕:发现——“你在哪?我在这”
配对完成只是建立了"互信关系",但设备之间还不知道对方的网络地址。就像你存了朋友的手机号,但每次打电话前还是得拨号。
发布-订阅模型
软总线发现用的是发布-订阅模式,像广播电台:
**智慧屏(被发现端)**调 PublishLNN 发布自己的能力:
PublishLNN(pkgName, publishInfo, publishCb)
它告诉软总线:"我叫智慧屏,我有 ddmpCapability 能力,谁要找我?"软总线会通过 BLE 广播、Wi-Fi CoAP 组播等方式把这个消息发出去。
**手机(发现端)**调 RefreshLNN 订阅感兴趣的能力:
RefreshLNN(pkgName, subscribeInfo, refreshCb)
它告诉软总线:“我在找有 ddmpCapability 能力的设备。”
四条发现通道
软总线不只有一种发现方式,它支持四种介质:
| 介质 | 原理 | 适合场景 |
|---|---|---|
| BLE | 蓝牙低功耗广播+扫描 | 功耗敏感的近场发现 |
| CoAP | Wi-Fi 局域网组播 | 同一 Wi-Fi 下快速发现 |
| USB | 有线连接枚举 | 需要物理连接的安全场景 |
| NFC | 近场触碰 | 碰一碰传 |
设为 AUTO 模式时,软总线会根据设备能力自动选择最佳介质,不用你操心。
发现结果
手机订阅后,软总线在后台扫描。发现智慧屏后,回调 OnDeviceFound 把设备信息推给手机:
OnDeviceFound(deviceInfo)
→ deviceInfo.deviceId = "智慧屏的设备ID"
→ deviceInfo.deviceName = "客厅智慧屏"
→ deviceInfo.extraData = {"WIFI_IP":"192.168.1.100", "WIFI_PORT":6666, ...}
注意那个 extraData——里面藏着智慧屏的 Wi-Fi IP 和端口,组网时要用。
发现阶段到此结束。 手机知道了智慧屏的存在和网络地址,但还不能发数据——因为还没组网。
第二幕:组网——“握个手,正式建交”
发现只是"看到对方",组网才是"正式建交"。组网成功后,设备会获得一个 networkId,这是后续传输的"门牌号"。
JoinLNN:组网入口
手机拿着智慧屏的地址信息,构造一个 ConnectionAddr,调 JoinLNN:
ConnectionAddr addr = {
.type = CONNECTION_ADDR_WLAN,
.info.ip = {"192.168.1.100", 6666}
};
JoinLNN(pkgName, &addr, OnJoinLNNResult);
JoinLNN 返回成功只表示软总线接受了请求,不代表组网已完成。真正的结果在回调里。
组网内部发生了什么?
这是整条链路中最复杂的一步。JoinLNN 触发后,软总线内部跑完一整套流程:
步骤 1:认证(Authentication)
虽然设备已经配对(互信关系已建立),但每次组网仍需进行身份认证和密钥协商。这是安全要求——互信关系是"知道你是谁",认证是"确认你真的是你"。
软总线的 core/authentication/ 模块调用 HiChain 引擎的 GroupAuthManager.AuthDevice(),基于已建立的互信关系完成:
- 身份验证(确认设备身份)
- 密钥协商(协商出会话密钥,用于后续加密传输)
认证过程走一个 4 状态有限状态机:
INIT → WAIT_CONNECTION → WAIT_REQUEST → CONNECTED
认证完成后,会话密钥生成,安全通道建立。
步骤 2:NetBuilder 构建拓扑
认证通过后,NetBuilder 子模块接管:
- 将新节点加入网络拓扑
- 分配
networkId(网络节点标识) - 同步拓扑信息(让网络中所有节点都知道新成员来了)
- 维护双账本:Local Ledger(本机视角)+ Online Ledger(网络视角)
步骤 3:心跳保活启动
节点上线后,心跳子模块开始工作,周期性发送心跳包检测对端在线状态:
| 档位 | 心跳间隔 | TCP 保活上限 | 适用场景 |
|---|---|---|---|
| 高频 | 30 秒 | 40 秒 | 活跃通信中 |
| 中频 | 60 秒 | 70 秒 | 一般使用 |
| 低频 | 5 分钟 | 315 秒 | 后台保活 |
| 默认 | 10 分钟 | 615 秒 | 省电模式 |
心跳超时后判定设备离线,自动清理节点信息。业务可以通过 ShiftLNNGear 调整心跳档位。
步骤 4:回调通知
组网完成后,两个回调先后触发:
OnJoinLNNResult(addr, networkId, 0) // retCode=0 表示成功
onNodeOnline(info) // 节点上线通知
手机拿到了智慧屏的 networkId——这是后续传输的关键凭证。
如果组网失败?
常见原因:
- 互信关系未建立(没配对就调 JoinLNN)→ 认证直接失败
- 设备不在同一局域网 → 连接超时
- 对端设备休眠 → 心跳无响应
组网失败时 OnJoinLNNResult 的 retCode 非 0,不会分配 networkId。
组网阶段到此结束。 手机和智慧屏在同一张分布式网络里了,有了各自的 networkId,可以开始传输。
第三幕:传输——“数据,出发!”
组网成功后,建立传输通道,数据才能真正流动。
Socket vs Session:选哪个?
软总线有两套传输接口:
| 接口 | 版本 | 连接方式 | 推荐度 |
|---|---|---|---|
| Socket | v2.0 | 同步 Bind | ⭐ 推荐 |
| Session | v1.0 | 异步 OpenSession | 旧版 |
Socket 接口更简洁,同步语义更清晰,是新业务的首选。下面的流程基于 Socket。
建立传输通道
传输是客户端-服务端模式。假设智慧屏做服务端,手机做客户端。
智慧屏(服务端):
// 1. 创建 Socket
SocketInfo info = {
.name = "mySession",
.peerName = "mySession",
.peerNetworkId = phoneNetworkId,
.pkgName = "com.example.cast",
.dataType = TYPE_BYTES
};
int32_t sock = Socket(info);
// 2. 监听,等待客户端连接
Listen(sock, qos, qosCount, &listener);
手机(客户端):
// 1. 创建 Socket
SocketInfo info = {
.name = "mySession",
.peerName = "mySession",
.peerNetworkId = screenNetworkId, // 智慧屏的 networkId
.pkgName = "com.example.cast",
.dataType = TYPE_BYTES
};
int32_t sock = Socket(info);
// 2. 绑定(连接服务端)
Bind(sock, qos, qosCount, &listener);
// 3. 绑定成功后,回调 OnBind 触发
void OnBind(int32_t socket, PeerSocketInfo info) {
// 通道建立成功,可以发数据了!
}
四条传输通道
Bind 之后,软总线的选路模块会自动选择最佳传输通道:
| 通道 | 适合数据 | 特点 |
|---|---|---|
| TCP | 消息、小文件 | 可靠传输,Wi-Fi P2P |
| UDP | 流、大文件 | 高带宽,支持 QoS |
| Proxy | 蓝牙等受限链路 | 服务端中继转发 |
| Auth | 认证阶段数据 | 加密传输 |
选路是自动的,业务不需要关心。软总线会根据链路质量、数据类型、QoS 要求综合决策。
发送数据
通道建立后,四种数据类型任你选:
SendBytes(sock, photoData, len) // 发字节(照片二进制)
SendMessage(sock, command, len) // 发消息(控制指令)
SendStream(sock, videoFrame, ...) // 发流(视频帧)
SendFile(sock, fileList, ...) // 发文件(大文件传输)
每种发送都有对应的接收回调:
void OnBytes(int32_t socket, const void *data, uint32_t len) {
// 收到字节数据
}
void OnMessage(int32_t socket, const void *data, uint32_t len) {
// 收到消息
}
QoS:给传输提要求
创建 Socket 时可以传入 QoS 参数,告诉软总线你的偏好:
QosTV qos[] = {
{QOS_TYPE_MIN_BW, 1024*1024}, // 最小带宽 1MB/s
{QOS_TYPE_MAX_WAIT_TIMEOUT, 5000}, // Bind 超时 5 秒
{QOS_TYPE_MIN_LATENCY, 100} // 最小建链时延 100ms
};
选路模块会参考 QoS 要求选择最合适的通道和链路。
传输阶段到此结束。 照片数据从手机发出,通过软总线通道,到达智慧屏。
第四幕:收尾——“好聚好散”
数据传完了,得优雅地关闭连接,释放资源:
// 1. 关闭传输通道
Shutdown(sock);
// 2. 退出组网
LeaveLNN(pkgName, networkId, OnLeaveLNNResult);
// 退网后 networkId 变为无效,不应继续使用
// 3. 停止发现
StopRefreshLNN(pkgName, refreshId); // 发现端
StopPublishLNN(pkgName, publishId); // 被发现端
Shutdown 触发 OnShutdown 回调,LeaveLNN 触发 OnLeaveLNNResult 回发。心跳停止,节点从拓扑中移除,onNodeOffline 通知对端。
全链路时序图
把四幕串起来,完整的端到端时序如下:
设备 A(手机/客户端) 设备 B(智慧屏/服务端)
───────────────── ─────────────────
【前置:配对已完成,互信关系已建立】
① 发现
PublishLNN(能力X)
RefreshLNN(订阅能力X)
←── 软总线发现 ──→
OnDeviceFound(B)
←── extraData(IP/Port) ──→
② 组网
JoinLNN(B.addr)
←── 认证(HiChain) ──→
←── 密钥协商 ──→
←── 拓扑同步 ──→
OnJoinLNNResult(netId_B) OnJoinLNNResult(netId_A)
onNodeOnline(B) onNodeOnline(A)
←── 心跳保活启动 ──→
③ 传输
sock = Socket(info)
Listen(sock, qos, listener)
sock = Socket(info)
Bind(sock, qos, listener)
←── 选路+建通道 ──→
←── OnBind ──→
SendBytes(sock, photoData)
──── 数据飞过去 ────→
OnBytes(sock, photoData)
←──── 处理完成回执 ────
OnBytes(sock, ack)
④ 收尾
Shutdown(sock) Shutdown(sock)
LeaveLNN(netId_B) LeaveLNN(netId_A)
StopRefreshLNN() StopPublishLNN()
←── 心跳停止 ──→
←── 节点下线 ──→
onNodeOffline(B) onNodeOffline(A)
那些容易踩的坑
坑一:跳过配对直接 JoinLNN。 互信关系没建立,认证直接失败,retCode 非 0。配对是前置条件,没有捷径。
坑二:JoinLNN 返回 0 就以为组网成功了。 JoinLNN 返回 0 只表示"请求被接受",真正的结果在 OnJoinLNNResult 回调里。拿到 networkId 才算成功。
坑三:退网后继续用 networkId。 LeaveLNN 成功后 networkId 变为无效,继续用它调传输接口会出错。
坑四:忘记注册节点状态回调。 RegNodeDeviceStateCb 不注册,onNodeOnline / onNodeOffline 不会触发,业务感知不到设备上下线。
坑五:传输完不 Shutdown。 Socket 不关闭,资源不释放,长期累积会导致句柄泄漏。
软总线到底屏蔽了什么?
回看整条链路,软总线帮业务屏蔽了这些复杂度:
- 链路差异:BLE / Wi-Fi / 蓝牙 / USB 的 API 各不相同,软总线统一了
- 安全认证:HiChain 协议握手、密钥协商、加密通道,业务不用自己搞
- 连接管理:建连、断线重连、心跳保活、离线检测,全自动化
- 通道选择:UDP / TCP / Proxy / Auth 四种通道,选路模块自动决策
- 拓扑维护:节点上下线、信息同步、双账本管理,业务无感
业务只需要:发布/订阅能力 → JoinLNN → Socket Bind → SendData。剩下的交给软总线。
更多推荐





所有评论(0)