商)
本篇不講那些設(shè)計模式架構(gòu)什么的感覺幾十萬行代碼看的人都暈了停了一周感覺之前理解方式錯了我剛開始想著熟悉事件流程出了問題可以判斷出在那個環(huán)節(jié)但是對于這種大型項目來說根本看不過來看了前面忘了后面。我和豆包聊了一會再結(jié)合之前和同事聊天決定換個方式從模塊的設(shè)計策略處理了什么難題獲得了什么舍棄了什么異常處理什么問題這些角度來分析。這是我接下來的策略本部分不會上傳到LavalLaval發(fā)布的其實都有裁剪過。多通道并行協(xié)商聽著很高大上其實還是挺好理解的首先要了解一些前置傳輸模塊是負(fù)責(zé)設(shè)備之間通信傳輸?shù)哪K在開始通信之前我記得之前內(nèi)部培訓(xùn)設(shè)備會先注冊到云端有什么發(fā)現(xiàn)發(fā)布流程那么就是在這個交互過程中會在本地有一個networkid記錄本地會拿著這個networkid去查詢對端信息創(chuàng)建一個sessionid這只是業(yè)務(wù)流程還沒真正開始因為還有appinfo等很多結(jié)構(gòu)體還沒裝配會話對應(yīng)著一個channel這里只能對應(yīng)一個channel這個是在最上層的結(jié)構(gòu)體寫死的一個會話只能負(fù)責(zé)一個channelsession會話與channel通道區(qū)別會話算是應(yīng)用層代表業(yè)務(wù)鏈路的上下文存著業(yè)務(wù)所需的像包名、權(quán)限、業(yè)務(wù)標(biāo)識這些的channel通道是真實承載數(shù)據(jù)流底層載體選擇 1:1 核心原因分布式業(yè)務(wù)場景天然是一單業(yè)務(wù)對應(yīng)一條數(shù)據(jù)流極大簡化狀態(tài)機(jī)、會話取消、資源回收、QoS、保序邏輯參考你 TransOpenChannel 代碼規(guī)避多通道帶來的報文亂序、資源競爭、生命周期管理復(fù)雜度如果業(yè)務(wù)需要多路數(shù)據(jù)流框架推薦新建多個 Session而不是單 Session 創(chuàng)建多條 Channel。channel通道與Lane車道區(qū)別通道是類似于管理器我要傳這個數(shù)據(jù)流通過什么方式傳輸而lane是具體的物理鏈路的代名詞比如是采取WiFi還是藍(lán)牙鏈路。一個通道channel可以對應(yīng)多個lane比如當(dāng)WiFi的質(zhì)量不太好的時候可以切換到藍(lán)牙傳輸或者像大數(shù)據(jù)量傳輸?shù)臅r候可以采取多個lane并發(fā)傳輸這樣效率更高這方面的算法還沒有分析可以先做一個埋點。channel通道與Lane車道創(chuàng)建時機(jī)之前在邏輯理解上是一個session后面接一個channel一個channel后面接著一個或者多個lane但是創(chuàng)建時機(jī)上會先申請lane再將LaneConnInfo轉(zhuǎn)換成ConnectOption用來創(chuàng)建channel通道。TransOpenChannel 被調(diào)用 │ ▼ ① 申請 Lane找路/修路 - TransGetLaneInfo / TransAsyncGetLaneInfo - 輸出laneHandle LaneConnInfo路的信息 │ ▼ ② Lane 申請成功了路有了 - 狀態(tài)置為 LAN_COMPLETE │ ▼ ③ 打開 Channel在這條路上開車道 - TransOpenChannelProc ← 你之前問的函數(shù) - 輸入LaneConnInfo 轉(zhuǎn)成的 ConnectOption - 輸出channelId │ ▼ ④ 把 channel 和 lane 綁定起來 - TransLaneMgrAddLaneLane復(fù)用機(jī)制復(fù)用機(jī)制核心數(shù)據(jù)結(jié)構(gòu)lnn_lane_link.htypedef struct { bool isServerSide; // 是服務(wù)端還是客戶端 uint32_t laneScore; // 車道評分質(zhì)量分 uint32_t laneFload; // 車道負(fù)載 uint32_t clientRef; // 【關(guān)鍵】客戶端引用計數(shù) LaneLinkInfo link; // 鏈路具體信息 ListNode node; uint64_t laneId; // 全局唯一lane ID } LaneResource;其中clientRef就是lane的引用計數(shù)。申請Lane流程lnn_lane_link.c申請 Lane │ ▼ 查資源池g_laneResource │ ├─ 有可用的同類型lane │ └─ 是 → clientRef → 直接返回 → 復(fù)用成功 │ └─ 沒有 → 新建鏈路 → 加入資源池 → clientRef 1 → 新建成功Lane選擇策略Lane選擇不一定是選擇復(fù)用 lnn_lane_select.c策略觸發(fā)條件行為reuseBestEffort手表設(shè)備等低功耗場景優(yōu)先復(fù)用已有 BR 鏈路不新建默認(rèn)鏈路普通請求按默認(rèn)優(yōu)先級選鏈路QoS 驅(qū)動指定了帶寬 / 延遲要求按 QoS 選最合適的鏈路RTT 優(yōu)先要求低延遲選 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); }多通道并行協(xié)商之前講到這個項目設(shè)計的是一個channel可以使用多個lane一個lane也可以被多個channel同時使用這里面就容易產(chǎn)生資源競爭的問題這個項目并不是采用簡單的多路復(fù)用機(jī)制來解決問題而是采用一套分層機(jī)制來實現(xiàn)核心機(jī)制同一物理鏈路只有一份資源記錄誰用誰加引用1. 去重基礎(chǔ)laneId 是確定性生成的GenerateLaneId 把兩端 udid 排序后拼接 linkType做哈希得到 64 位 laneId同一對設(shè)備 同一種鏈路類型 →laneId 永遠(yuǎn)相同所以不管多少個 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] 構(gòu)造字節(jié)數(shù)組 laneIdParamBytes bigUdid smallUdid linkType (共 LANE_ID_BUF_LEN 字節(jié)) ↓ [3] 生成 Hash 調(diào)用 SoftBusGenerateStrHash 對字節(jié)數(shù)組做 Hash ↓ [4] 返回 Hash 值作為 Lane ID (uint64_t)2. 資源池 引用計數(shù)核心全局資源池g_laneResource一個鏈表每次建鏈成功后調(diào)用 AddLaneResourceToPool按(linkType peerUdid 鏈路地址)查池GetValidLaneResource已存在→ UpdateExistLaneResourceclientRef復(fù)用同一條物理鏈路不存在→ 新建節(jié)點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. 互斥鎖每個共享數(shù)據(jù)區(qū)一把鎖鎖位置保護(hù)對象g_laneMutexlnn_lane.c:75laneReqId 位圖、listener 鏈表g_laneResource.locklnn_lane_link.c:66資源池增刪改查g_p2pLinkMutexlnn_lane_link_p2p.c:161進(jìn)行中的 P2P 建鏈/拆鏈請求g_linkConflictList.locklnn_lane_link_conflict.c:40沖突記錄表注意所有查找都是鎖內(nèi) memcpy 副本、鎖外使用如 FindLaneResourceByLaneId:665-690避免鎖釋放后懸垂指針。4. P2P/WiFiDirect 連接的并發(fā)合并LnnConnectP2p 是先嘗試復(fù)用、不復(fù)用才建鏈/foundation/communication/dsoftbus/core/bus_center/lnn/lane_hub/lane_manager/src/lnn_lane_link_p2p.cTryWifiDirectReuse查資源池發(fā)現(xiàn) (peerUdid, linkType) 已有 lane 且 wifi direct 判定無需重新協(xié)商信道 →直接掛載到已有連接上不發(fā)起新連接失敗才走 SelectGuideChannel 協(xié)商建鏈且建鏈請求本身也記錄在g_p2pLinkList里做進(jìn)行中去重if (TryWifiDirectReuse(request, laneReqId, callback) SOFTBUS_OK) { return SOFTBUS_OK; } return SelectGuideChannel(request, laneReqId, callback);5. 物理層沖突的記錄 避讓手機(jī)場景真正稀缺的是射頻資源所以還有一層沖突管理lnn_lane_link_conflict.cGetConflictTypeWithErrcode:155 把建鏈?zhǔn)″e誤碼歸類為角色沖突GO/GC、鏈路數(shù)上限LNN_LANE_P2P_MAX_NUM 4、三 VAP 沖突、軟 AP 芯片沖突AddLinkConflictInfo記錄沖突帶30 秒時效CONFLICT_INFO_TIMELINESS超時自動刪除后續(xù)分配時查FindLinkConflictInfoByDevId避讓剛沖突過的設(shè)備釋放 HML 時用CheckLinkConflictByReleaseLink判斷是否有排隊請求等著這條鏈路6. 雙端對稱注冊防止一端拆一端用P2P/HML 是雙端協(xié)商的鏈路兩端的設(shè)備都會各自把這條鏈路注冊進(jìn)自己的資源池。LaneResource.isServerSide標(biāo)記這條鏈路是誰發(fā)起的server 端即使本地 clientRef 歸零也不會拆物理鏈路IsNeedDelResource:458-474只有等對端釋放才清理 —— 避免了雙端時序不一致導(dǎo)致的競爭。/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; }結(jié)語這里還只是channel和lane層面的多路控制處理策略我估計在傳輸層的發(fā)送那邊可能會有多路復(fù)用、時序發(fā)送等處理策略避免資源競爭看后面驗證。