news 2026/8/5 10:58:00

软总线-传输模块-多通道并行协商

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
软总线-传输模块-多通道并行协商

本篇不讲那些设计模式,架构什么的,感觉几十万行代码,看的人都晕了,停了一周,感觉之前理解方式错了,我刚开始想着熟悉事件流程,出了问题可以判断出在那个环节,但是对于这种大型项目来说,根本看不过来,看了前面忘了后面。我和豆包聊了一会,再结合之前和同事聊天,决定换个方式,从模块的设计策略,处理了什么难题,获得了什么,舍弃了什么,异常处理什么问题,这些角度来分析。这是我接下来的策略,本部分不会上传到Laval,Laval发布的其实都有裁剪过。

多通道并行协商听着很高大上,其实还是挺好理解的,首先要了解一些前置,传输模块是负责设备之间通信传输的模块,在开始通信之前,我记得之前内部培训,设备会先注册到云端,有什么发现发布流程,那么就是在这个交互过程中会在本地有一个networkid记录,本地会拿着这个networkid去查询对端信息,创建一个sessionid(这只是业务流程,还没真正开始,因为还有appinfo等很多结构体还没装配),会话对应着一个channel,这里只能对应一个channel,这个是在最上层的结构体写死的,一个会话只能负责一个channel

session会话与channel通道区别

会话算是应用层,代表业务链路的上下文,存着业务所需的像包名、权限、业务标识这些的

channel通道是真实承载数据流底层载体

  • 选择 1:1 核心原因:
    • 分布式业务场景天然是一单业务对应一条数据流;
    • 极大简化状态机、会话取消、资源回收、QoS、保序逻辑(参考你 TransOpenChannel 代码);
    • 规避多通道带来的报文乱序、资源竞争、生命周期管理复杂度;
  • 如果业务需要多路数据流,框架推荐新建多个 Session,而不是单 Session 创建多条 Channel。

channel通道与Lane车道区别

通道是类似于管理器,我要传这个数据流,通过什么方式传输,而lane是具体的物理链路的代名词,比如是采取WiFi还是蓝牙链路。

一个通道channel可以对应多个lane,比如当WiFi的质量不太好的时候可以切换到蓝牙传输,或者像大数据量传输的时候,可以采取多个lane并发传输,这样效率更高,这方面的算法还没有分析,可以先做一个埋点。

channel通道与Lane车道创建时机

之前在逻辑理解上,是一个session后面接一个channel,一个channel后面接着一个或者多个lane,但是创建时机上,会先申请lane,再将LaneConnInfo转换成ConnectOption,用来创建channel通道。

TransOpenChannel 被调用 │ ▼ ① 申请 Lane(找路/修路) - TransGetLaneInfo / TransAsyncGetLaneInfo - 输出:laneHandle + LaneConnInfo(路的信息) │ ▼ ② Lane 申请成功了,路有了 - 状态置为 LAN_COMPLETE │ ▼ ③ 打开 Channel(在这条路上开车道) - TransOpenChannelProc ← 你之前问的函数 - 输入:LaneConnInfo 转成的 ConnectOption - 输出:channelId │ ▼ ④ 把 channel 和 lane 绑定起来 - TransLaneMgrAddLane

Lane复用机制

复用机制核心数据结构:lnn_lane_link.h

typedef struct { bool isServerSide; // 是服务端还是客户端 uint32_t laneScore; // 车道评分(质量分) uint32_t laneFload; // 车道负载 uint32_t clientRef; // 【关键】客户端引用计数 LaneLinkInfo link; // 链路具体信息 ListNode node; uint64_t laneId; // 全局唯一lane ID } LaneResource;

其中clientRef就是lane的引用计数。

申请Lane流程

lnn_lane_link.c

申请 Lane │ ▼ 查资源池(g_laneResource) │ ├─ 有可用的同类型lane? │ └─ 是 → clientRef++ → 直接返回 → 复用成功 │ └─ 没有 → 新建链路 → 加入资源池 → clientRef = 1 → 新建成功

Lane选择策略

Lane选择不一定是选择复用 lnn_lane_select.c

策略触发条件行为
reuseBestEffort手表设备等低功耗场景优先复用已有 BR 链路,不新建
默认链路普通请求按默认优先级选链路
QoS 驱动指定了带宽 / 延迟要求按 QoS 选最合适的链路
RTT 优先要求低延迟选 RTT 最低的链路
SelectExpectLanesByQos: if (request->qosRequire.reuseBestEffort) { ret = DecideReuseLane(networkId, request, &laneLinkList); } else if (request->qosRequire.minBW == 0 && ...) { ret = DecideDefaultLink(...); } else if (request->qosRequire.rttLevel == LANE_RTT_LEVEL_LOW) { ret = DecideCustomLink(networkId, CUSTOM_QOS_RTT, ...); } else { ret = DecideAvailableLane(networkId, request, &laneLinkList); }

多通道并行协商

之前讲到这个项目设计的是一个channel可以使用多个lane,一个lane也可以被多个channel同时使用,这里面就容易产生资源竞争的问题,这个项目并不是采用简单的多路复用机制来解决问题,而是采用一套分层机制来实现

核心机制:同一物理链路只有一份资源记录,谁用谁加引用

1. 去重基础:laneId 是确定性生成的

GenerateLaneId 把两端 udid 排序后拼接 + linkType,做哈希得到 64 位 laneId:

  • 同一对设备 + 同一种链路类型 →laneId 永远相同
  • 所以不管多少个 channel 请求,最终都落到同一个LaneResource上,这是"一个 lane 被多个 channel 共享"的根基
uint64_t GenerateLaneId(const char *localUdid, const char *remoteUdid, LaneLinkType linkType) { if (localUdid == NULL || remoteUdid == NULL) { LNN_LOGE(LNN_LANE, "udid is NULL"); return SOFTBUS_INVALID_PARAM; } const char *bigUdid = NULL; const char *smallUdid = NULL; if (strcmp(localUdid, remoteUdid) >= 0) { bigUdid = localUdid; smallUdid = remoteUdid; } else { bigUdid = remoteUdid; smallUdid = localUdid; } uint8_t laneIdParamBytes[LANE_ID_BUF_LEN]; (void)memset_s(laneIdParamBytes, sizeof(laneIdParamBytes), 0, sizeof(laneIdParamBytes)); uint64_t laneId = INVALID_LANE_ID; uint16_t type = (uint16_t)linkType; // sharded copy, LANE_ID_BUF_LEN = UDID_BUF_LEN + UDID_BUF_LEN + TYPE_BUF_LEN if (memcpy_s(laneIdParamBytes, UDID_BUF_LEN, bigUdid, strlen(bigUdid)) == EOK && memcpy_s(laneIdParamBytes + UDID_BUF_LEN, UDID_BUF_LEN, smallUdid, strlen(smallUdid)) == EOK && memcpy_s(laneIdParamBytes + UDID_BUF_LEN + UDID_BUF_LEN, TYPE_BUF_LEN, &type, sizeof(type)) == EOK) { uint8_t laneIdHash[LANE_ID_HASH_LEN] = {0}; if (SoftBusGenerateStrHash(laneIdParamBytes, LANE_ID_BUF_LEN, laneIdHash) != SOFTBUS_OK) { LNN_LOGE(LNN_LANE, "generate laneId hash fail"); return INVALID_LANE_ID; } uint32_t len = sizeof(laneId) <= LANE_ID_HASH_LEN ? sizeof(laneId) : LANE_ID_HASH_LEN; if (memcpy_s(&laneId, sizeof(laneId), laneIdHash, len) != EOK) { LNN_LOGE(LNN_LANE, "memcpy laneId hash fail"); return INVALID_LANE_ID; } char *anonyLocalUdid = NULL; char *anonyRemoteUdid = NULL; Anonymize(localUdid, &anonyLocalUdid); Anonymize(remoteUdid, &anonyRemoteUdid); LNN_LOGI(LNN_LANE, "generate laneId=%{public}" PRIu64 " with localUdid=%{public}s," "remoteUdid=%{public}s, linkType=%{public}d", laneId, AnonymizeWrapper(anonyLocalUdid), AnonymizeWrapper(anonyRemoteUdid), linkType); AnonymizeFree(anonyLocalUdid); AnonymizeFree(anonyRemoteUdid); return laneId; } LNN_LOGE(LNN_LANE, "memcpy laneId param bytes fail"); return INVALID_LANE_ID; }
输入: localUdid, remoteUdid, linkType ↓ [1] 字符串排序 确保一致性 - 用 strcmp 比较两个 UDID - 较大的放前面 (bigUdid) - 较小的放后面 (smallUdid) ↓ [2] 构造字节数组 laneIdParamBytes = bigUdid + smallUdid + linkType (共 LANE_ID_BUF_LEN 字节) ↓ [3] 生成 Hash 调用 SoftBusGenerateStrHash 对字节数组做 Hash ↓ [4] 返回 Hash 值作为 Lane ID (uint64_t)

2. 资源池 + 引用计数(核心)

全局资源池g_laneResource(一个链表),每次建链成功后调用 AddLaneResourceToPool:

  • (linkType + peerUdid + 链路地址)查池(GetValidLaneResource)
  • 已存在→ UpdateExistLaneResource:clientRef++,复用同一条物理链路
  • 不存在→ 新建节点,clientRef = 1

释放时 DelLaneResourceByLaneId 走 IsNeedDelResource:--clientRef只有clientRef == 0且不是 server 端时才真正删除资源(此时才去拆 P2P 连接)。

int32_t AddLaneResourceToPool(const LaneLinkInfo *linkInfo, uint64_t laneId, bool isServerSide) { if (linkInfo == NULL || laneId == INVALID_LANE_ID) { LNN_LOGE(LNN_LANE, "linkInfo is nullptr or invalid laneId"); return SOFTBUS_INVALID_PARAM; } if (LaneLock() != SOFTBUS_OK) { LNN_LOGE(LNN_LANE, "lane lock fail"); return SOFTBUS_LOCK_ERR; } int32_t addResult = SOFTBUS_LANE_RESOURCE_EXCEPT; LaneResource* resourceItem = GetValidLaneResource(linkInfo); if (resourceItem != NULL) { addResult = UpdateExistLaneResource(resourceItem, isServerSide); LaneUnlock(); return addResult; } LaneUnlock(); addResult = CreateNewLaneResource(linkInfo, laneId, isServerSide); if (addResult != SOFTBUS_OK) { LNN_LOGE(LNN_LANE, "create laneResource fail, result=%{public}d", addResult); return addResult; } if (!isServerSide) { AddNetworkResourceInner(linkInfo, laneId); } return SOFTBUS_OK; }
查找资源 GetValidLaneResource ↓ ┌─────┴─────┐ │ 存在? │ └─────┬─────┘ Yes │ No ┌────┴────┐ ↓ ↓ [存在]更新资源 [不存在] UpdateExist 先 LaneUnlock() Lane 再 CreateNew Resource NewLaneResource ↓ ↓ LaneUnlock return ↓ return

3. 互斥锁:每个共享数据区一把锁

位置保护对象
g_laneMutexlnn_lane.c:75laneReqId 位图、listener 链表
g_laneResource.locklnn_lane_link.c:66资源池增删改查
g_p2pLinkMutexlnn_lane_link_p2p.c:161进行中的 P2P 建链/拆链请求
g_linkConflictList.locklnn_lane_link_conflict.c:40冲突记录表

注意所有查找都是锁内 memcpy 副本、锁外使用(如 FindLaneResourceByLaneId:665-690),避免锁释放后悬垂指针。

4. P2P/WiFiDirect 连接的并发合并

LnnConnectP2p 是先尝试复用、不复用才建链:(/foundation/communication/dsoftbus/core/bus_center/lnn/lane_hub/lane_manager/src/lnn_lane_link_p2p.c)

  1. TryWifiDirectReuse:查资源池发现 (peerUdid, linkType) 已有 lane 且 wifi direct 判定无需重新协商信道 →直接挂载到已有连接上,不发起新连接
  2. 失败才走 SelectGuideChannel 协商建链,且建链请求本身也记录在g_p2pLinkList里做进行中去重
if (TryWifiDirectReuse(request, laneReqId, callback) == SOFTBUS_OK) { return SOFTBUS_OK; } return SelectGuideChannel(request, laneReqId, callback);

5. 物理层冲突的"记录 + 避让"

手机场景真正稀缺的是射频资源,所以还有一层冲突管理(lnn_lane_link_conflict.c):

  • GetConflictTypeWithErrcode:155 把建链失败错误码归类为:角色冲突(GO/GC)、链路数上限LNN_LANE_P2P_MAX_NUM = 4)、三 VAP 冲突、软 AP 芯片冲突
  • AddLinkConflictInfo记录冲突,带30 秒时效CONFLICT_INFO_TIMELINESS),超时自动删除
  • 后续分配时查FindLinkConflictInfoByDevId避让刚冲突过的设备;释放 HML 时用CheckLinkConflictByReleaseLink判断是否有排队请求等着这条链路

6. 双端对称注册,防止"一端拆一端用"

P2P/HML 是双端协商的,链路两端的设备都会各自把这条链路注册进自己的资源池。LaneResource.isServerSide标记这条链路是谁发起的:server 端即使本地 clientRef 归零也不会拆物理链路(IsNeedDelResource:458-474),只有等对端释放才清理 —— 避免了双端时序不一致导致的竞争。(/foundation/communication/dsoftbus/core/bus_center/lnn/lane_hub/lane_manager/src/lnn_lane_link.c)

static bool IsNeedDelResource(uint64_t laneId, bool isServerSide, LaneResource *item) { if (item->laneId != laneId) { return false; } uint32_t ref = 0; bool isServer = false; if (isServerSide) { ref = item->clientRef; if (item->clientRef == 0) { ListDelete(&item->node); SoftBusFree(item); if (g_laneResource.cnt != 0) { g_laneResource.cnt--; } } else { item->isServerSide = false; } } else { isServer = item->isServerSide; ref = item->clientRef; if (item->clientRef != 0) { ref = --item->clientRef; } if (!isServer && ref == 0) { DeleteNetworkResourceByLaneId(laneId); ListDelete(&item->node); SoftBusFree(item); if (g_laneResource.cnt != 0) { g_laneResource.cnt--; } } } LNN_LOGI(LNN_LANE, "del laneId=%{public}" PRIu64 " resource, isServer=%{public}d, clientRef=%{public}u", laneId, isServer, ref); return true; }

结语

这里还只是channel和lane层面的多路控制处理策略,我估计在传输层的发送那边可能会有多路复用、时序发送等处理策略避免资源竞争,看后面验证。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/5 10:57:29

Agentic架构下C#与Go的分层选型策略:从编排到执行的全栈实践

1. 项目概述&#xff1a;当Agentic遇上语言选型 最近和几个做AI应用架构的朋友聊天&#xff0c;大家讨论最激烈的一个话题就是&#xff1a;在Agentic&#xff08;智能体驱动&#xff09;架构逐渐成为主流的今天&#xff0c;后端技术栈到底该怎么选&#xff1f;尤其是核心业务逻…

作者头像 李华
网站建设 2026/8/5 10:56:15

从 list 到混合存储:1 亿个布尔标签的 5 次优化踩坑实录

前阵子做用户画像系统&#xff0c;遇到一个看起来特别简单的需求&#xff1a;存 1 亿个用户的布尔标签。不就是 True/False 吗&#xff1f;我当时想&#xff0c;这有什么难的&#xff0c;一行代码的事。结果前前后后优化了 5 版&#xff0c;每一版都觉得“这下总完美了吧”&…

作者头像 李华
网站建设 2026/8/5 10:55:30

淘客APP分布式任务调度:海量商品更新的定时任务优化方案

淘客APP分布式任务调度&#xff1a;海量商品更新的定时任务优化方案 大家好&#xff0c;我是省赚客APP研发者微赚淘客&#xff01; 在返利业务中&#xff0c;商品信息的时效性直接决定了用户的转化率和平台的信誉。一个包含数百万甚至上千万商品的库&#xff0c;需要定时进行价…

作者头像 李华
网站建设 2026/8/5 10:55:20

SpringBoot+Vue3构建古典舞在线交流平台全栈实践

1. 项目概述&#xff1a;古典舞在线交流平台的技术架构 这套古典舞在线交流平台系统采用了当前Java Web领域最主流的全栈技术组合&#xff1a;后端基于SpringBoot 2框架构建&#xff0c;前端使用Vue3实现响应式界面&#xff0c;数据持久层采用MyBatis-Plus简化数据库操作&#…

作者头像 李华
网站建设 2026/8/5 10:54:45

全志芯片开发必备:编译sunxi-tools与FEL模式实战指南

1. 为什么我们需要自己编译 sunxi-tools&#xff1f; 如果你玩过全志&#xff08;Allwinner&#xff09;方案的开发板&#xff0c;比如大名鼎鼎的香橙派&#xff08;Orange Pi&#xff09;、荔枝派&#xff08;Lichee Pi&#xff09;&#xff0c;或者一些早期的平板、电视盒子&…

作者头像 李华
网站建设 2026/8/5 10:51:56

CasADi与MPC实现车辆轨迹跟踪控制

1. 项目概述&#xff1a;当CasADi遇上车辆轨迹跟踪在自动驾驶和智能车辆控制领域&#xff0c;轨迹跟踪一直是个经典难题。我最近用CasADi框架实现了一个基于模型预测控制&#xff08;MPC&#xff09;的解决方案&#xff0c;专门针对简化后的质点车辆模型。这个方案在Matlab环境…

作者头像 李华