一種基於WebRTC與UDP組播的一對多遠端控制桌面的實現思路
筆者最近收到要求遠端控制區域網內多N臺終端同步操作 的需求(功能類似windows上的遠端桌面)。 最終筆者實現了一種不太成熟,不太穩定 ,但基本滿足現階段需求的方案,且未來會持續迭代。
出於種種複雜原因,筆者無法使用現在一些市面上很成熟的解決方案。但由於該專案僅限於內部人員使用,即對方案的成熟性,可靠性,可維護性沒有太多要求(面部表情逐漸舒展)。
分析
遠端控制桌面,無論是1對1還是1對多。核心的資料的無非就是倆類。音視訊資訊,控制資訊 (鍵盤,滑鼠,快捷鍵等)。當得到這倆個數據時,我們就可以在控制端實時獲取受控端的視訊資訊以及傳輸控制指令 。那麼接下的操作就是:
1.獲取被控制端的視訊流
作為一個前端開發工程師,對於如何獲取裝置的音視訊資訊這件事,本能的我就想起了WebRTC (網頁即時通訊)協議。
然後就是分析具體的裝置和場景是否適用,一頓腦補分析後,我看行(主要是WebRTC本身強大)。
2.傳送與接收控制資訊
由於具體業務和某些神祕的限定和權衡。採用TCP協議傳輸訊息是不可能的。那沒啥好說的了,UDP救我!
雖然他們一次次給我帶來的都是,這個不行,那個也不行 的種種限制,但是好訊息也是有的,在一系列複雜的操作後,我拿到了受控方模擬滑鼠和鍵盤操作的介面。得,齊活!
UDP傳輸不可靠怎麼辦?
開發初期,筆者遇到的最大的問題就是UDP的傳輸是無連線的,不保證可靠性,但是在該業務場景下多個受控端需要對比一段操作後的不同來上報異常 ,如果因為網路的問題導致接收到的指令又略微不同,那可用性就會大的降低 。
眾所周知,UDP的傳輸是不可靠的,它不像TCP那樣保證資料有序,不丟失的傳輸。但是限制就是在那裡,讓你頭大-.-。 那麼問題來了如何在該場景下確保UDP可靠傳輸 ?
如何解決該場景下UDP傳輸不可靠問題
在經過一系列的腦補過後,我的就在腦海裡產生了以下對話 :
(1) 毫無頭緒階段
A:一個服務端控制多個客戶端,他們之間怎麼傳資料? B:UDP組播? A:業務允許丟包嗎? B:不允許,而且還要有序執行。 複製程式碼
總結:該場景下筆者只能使用UDP組播/廣播,但又不允許發生丟包。
(2) 提出設想階段
A:傳送端每發一條訊息,接收端回服一條確認收到? B:服務端一條訊息發出去,然後接受幾十條確認訊息?如果一秒傳送30條資料呢? A: 一臺客戶端通過組播發送丟包請求後,其他客戶端接收到後就不傳送相同丟包? B:多臺客戶端同時丟包怎麼辦?隨機等待時間傳送嗎?這樣做的話,延遲呢?不同步怎麼辦?而且等待的時間是不是還得監聽通道? 複製程式碼
總結:模仿TCP的確認機制基本不可行的,需要走其他的路。
(3) 初步決定方案A
A:如果客戶端不能回覆給服務端確定收到的訊息,那就只能客戶端自己確定丟包? B:對,客戶端需要判斷自己丟包,然後反饋給服務端! A:客戶端唯一的資訊來源就是服務端以往發的包,所以就是客戶端發的包之間自帶聯絡。 B:客戶端發包的時候加上包的Index?然後客戶端根據上下倆個包Index是否是連續的來判斷是否丟包? 複製程式碼
總結:客戶端發包的時加上遞增的Index,服務端通過Index判斷是否丟包。
(4) 緩衝區的建立和維護
B: 比如現在伺服器下發了包1,2,3後又下發4. 客戶端1收到了全部的訊息,它保持沉默。 客戶端2丟失了資訊3,當收到資訊4時,它發現前一個包是資訊2.於是它就會去給伺服器發丟失資訊3的反饋包? A: 怎麼發?點對點還是走組播? B:走組播,如果其它接收端也丟了這個包,就可以收到了。沒丟也可以判斷Index來得知這是一個已執行過的包。 同時服務端需要維護一個緩衝區。需要清除已確認傳送成功的資料,不然記憶體會爆。 A:可服務端沒辦法確定接收端端包是否收到的啊? B:如果丟包的機制生效。當客戶端反饋包9丟失,就意味著包9之前的包該客戶端都收到了。那就可以把之前的包清除了。 當然也需要設定一個最大值。 A:可是存在多個傳送端啊!其中一個收到後,服務端清除該包。一會後另一臺客戶端又反饋丟了該包,怎麼辦? B:和已經反饋過收到的客戶端要唄! 複製程式碼
總結:服務端維護一個具有最大長度的緩衝區,並在確認收到後清除無效資料。
(5) 等待和強制執行機制
A:回到剛才,客戶端收到資訊2後緊接著收到資訊4,判斷資訊3丟了。傳送資訊3的再次傳送請求時,已接收到的資訊4怎麼辦? 如果在等待資訊3的過程中,資訊5,資訊6,資訊7都接受到了有如何處理?一直等著嗎?還是一個倆個包就忽略丟失? B:絕對不能忽略!因為客戶端不知道丟的包是什麼操作,如果是點選,忽視會導致客戶端之間資訊不同步很嚴重!所以只能等。 同事在收到包後,檢測是否有更大Index的包存在。如果有,強制執行該包。 複製程式碼
總結:客戶端同樣維護一個緩衝區,丟包時等待同時接受新的資料。收到包後強制執行所有已收到的包。
(6) 多個通道
B:這樣的話,所有的客戶端收到的訊息都是一樣的,大家的包Index都是同一個。但是如果此時需要點對點通訊怎麼辦? A:每個客戶端都維護一套緩衝區? B:不可能,成本太大。私聊走私聊通道,可以不確保可靠性。 複製程式碼
總結:服務端需要有組播和點對點通訊倆套機制,點對點可以不確保可靠性。
(7) 包加權
A:丟包之後等著回傳有可能導致延遲太大,多個客戶端不同步。 B:逆推,不需要等 --> 忽視丟包 --> 丟失包不重要 --> 你判斷出包不重要。 服務端發包時帶上過往的N個包的權重。這樣客戶端接受到包時就知道前面的包的權重了! A:因為你是通過下一個包來判斷上一個包是否丟包的,所以如果下一個包攜帶之前N個包的重要程度。理論上就可能忽視丟包了! B:所以,需要在傳送端給包的許可權分等級。比如,滑鼠移動等級為1,滑鼠點選為10。 複製程式碼
總結:為了可以做到忽視丟包,需要確定每一個包的權重,做法就是在每一個包里加上之前N個包的權重。
(8) 合包和拆包
A:有一個需求將已段時間的操作儲存。然後隨時可以一次性全部發送給接收端。 B:一次性發送大的資料會導致包在IP層被分片,包越大被分的片越多。該業務指令碼會有多大? A:和錄製時長成正比,而且使用者操作不可揣測,且不該被限定。所以我們需要在服務端進行手動拆包在客戶端進行合包。 B:所以需要給拆分的包加上總包長,該包在總包中的Index等。 A:拆分過的包傳送時依然遵循上述可靠協議傳送,確保可以收到所有的分包。 複製程式碼
總結:為了避免在IP層被分片,需要服務端去手動分包,然後在客戶端進行合包。
實現過程中遇到的那些問題
問題1 客戶端滑鼠卡頓
出於對傳送資訊不能太頻繁的考量,滑鼠移動的過程中傳送資料做了節流 。代價就是接收端的滑鼠收到了一群不連續的的位置點。由於服務端是通過WebRTC顯示某一臺客戶端的畫面來進行操作的,所以使用者使用體驗很差。
解決思路:
如果服務端做了滑鼠移動事件節流,那麼客戶端只需要做滑鼠移動插值就行了。即在接受到的倆個滑鼠移動事件中,自己計算中間值進行插值。實測優化效果非常明顯。
問題2 客戶端短時間內反覆傳送丟包導致服務端負載大
在除錯過程中,發現客戶端在丟失資料包到接受到該丟失包的過程中,會多次的傳送丟包資訊給服務端。而後服務端又會在一段時間後返回多條資料(因為收到了多條丟包請求重傳的包).