两台设备配对成功后,数据是怎么从一台飞到另一台的?本文带你走完从鉴权完成到数据落地的完整旅程。

不是概览,不是缩略,是每一步都在的完整流程——但尽量讲得有意思一点。

故事背景

假设我们有两台设备:手机(设备 A)和智慧屏(设备 B)。它们已经通过 PIN 码完成了配对,互信关系建好了,ACL 表里互相是"可信设备"。

现在手机想把一张照片投到智慧屏上。这张照片要怎么过去?

答案是走软总线。但软总线不是"配对完了就能发数据"那么简单——中间还要经过发现、组网、传输三个阶段,每个阶段都有自己的故事。

第一幕:发现——“你在哪?我在这”

配对完成只是建立了"互信关系",但设备之间还不知道对方的网络地址。就像你存了朋友的手机号,但每次打电话前还是得拨号。

发布-订阅模型

软总线发现用的是发布-订阅模式,像广播电台:

**智慧屏(被发现端)**调 PublishLNN 发布自己的能力:

PublishLNN(pkgName, publishInfo, publishCb)

它告诉软总线:"我叫智慧屏,我有 ddmpCapability 能力,谁要找我?"软总线会通过 BLE 广播、Wi-Fi CoAP 组播等方式把这个消息发出去。

**手机(发现端)**调 RefreshLNN 订阅感兴趣的能力:

RefreshLNN(pkgName, subscribeInfo, refreshCb)

它告诉软总线:“我在找有 ddmpCapability 能力的设备。”

四条发现通道

软总线不只有一种发现方式,它支持四种介质:

介质原理适合场景
BLE蓝牙低功耗广播+扫描功耗敏感的近场发现
CoAPWi-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:选哪个?

软总线有两套传输接口:

接口版本连接方式推荐度
Socketv2.0同步 Bind⭐ 推荐
Sessionv1.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。剩下的交给软总线。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐