本篇不讲那些设计模式,架构什么的,感觉几十万行代码,看的人都晕了,停了一周,感觉之前理解方式错了,我刚开始想着熟悉事件流程,出了问题可以判断出在那个环节,但是对于这种大型项目来说,根本看不过来,看了前面忘了后面。我和豆包聊了一会,再结合之前和同事聊天,决定换个方式,从模块的设计策略,处理了什么难题,获得了什么,舍弃了什么,异常处理什么问题,这些角度来分析。这是我接下来的策略,本部分不会上传到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_laneMutex | lnn_lane.c:75 | laneReqId 位图、listener 链表 |
g_laneResource.lock | lnn_lane_link.c:66 | 资源池增删改查 |
g_p2pLinkMutex | lnn_lane_link_p2p.c:161 | 进行中的 P2P 建链/拆链请求 |
g_linkConflictList.lock | lnn_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)
- TryWifiDirectReuse:查资源池发现 (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.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层面的多路控制处理策略,我估计在传输层的发送那边可能会有多路复用、时序发送等处理策略避免资源竞争,看后面验证。

被折叠的 条评论
为什么被折叠?



