尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

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

软总线-传输模块-多通道并行协商 本篇不讲那些设计模式架构什么的感觉几十万行代码看的人都晕了停了一周感觉之前理解方式错了我刚开始想着熟悉事件流程出了问题可以判断出在那个环节但是对于这种大型项目来说根本看不过来看了前面忘了后面。我和豆包聊了一会再结合之前和同事聊天决定换个方式从模块的设计策略处理了什么难题获得了什么舍弃了什么异常处理什么问题这些角度来分析。这是我接下来的策略本部分不会上传到LavalLaval发布的其实都有裁剪过。多通道并行协商听着很高大上其实还是挺好理解的首先要了解一些前置传输模块是负责设备之间通信传输的模块在开始通信之前我记得之前内部培训设备会先注册到云端有什么发现发布流程那么就是在这个交互过程中会在本地有一个networkid记录本地会拿着这个networkid去查询对端信息创建一个sessionid这只是业务流程还没真正开始因为还有appinfo等很多结构体还没装配会话对应着一个channel这里只能对应一个channel这个是在最上层的结构体写死的一个会话只能负责一个channelsession会话与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 绑定起来 - TransLaneMgrAddLaneLane复用机制复用机制核心数据结构lnn_lane_link.htypedef 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已存在→ UpdateExistLaneResourceclientRef复用同一条物理链路不存在→ 新建节点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 ↓ return3. 互斥锁每个共享数据区一把锁锁位置保护对象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.cTryWifiDirectReuse查资源池发现 (peerUdid, linkType) 已有 lane 且 wifi direct 判定无需重新协商信道 →直接挂载到已有连接上不发起新连接失败才走 SelectGuideChannel 协商建链且建链请求本身也记录在g_p2pLinkList里做进行中去重if (TryWifiDirectReuse(request, laneReqId, callback) SOFTBUS_OK) { return SOFTBUS_OK; } return SelectGuideChannel(request, laneReqId, callback);5. 物理层冲突的记录 避让手机场景真正稀缺的是射频资源所以还有一层冲突管理lnn_lane_link_conflict.cGetConflictTypeWithErrcode: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.cstatic 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层面的多路控制处理策略我估计在传输层的发送那边可能会有多路复用、时序发送等处理策略避免资源竞争看后面验证。
返回列表