- 蘇格團隊
- 做者:Jason
筆者最近收到要求 遠程控制局域網內多N臺終端同步操做 的需求(功能相似windows上的遠程桌面)。 最終筆者實現了一種不太成熟,不太穩定,但基本知足現階段需求的方案,且將來會持續迭代。前端
出於種種複雜緣由,筆者沒法使用如今一些市面上很成熟的解決方案。但因爲該項目僅限於內部人員使用,即對方案的成熟性,可靠性,可維護性沒有太多要求(面部表情逐漸舒展)。windows
遠程控制桌面,不管是1對1仍是1對多。核心的數據的無非就是倆類。音視頻信息,控制信息 (鍵盤,鼠標,快捷鍵等)。當獲得這倆個數據時,咱們就能夠 在控制端實時獲取受控端的視頻信息以及傳輸控制指令 。那麼接下的操做就是:bash
做爲一個前端開發工程師,對於如何獲取設備的音視頻信息這件事,本能的我就想起了 WebRTC (網頁即時通信)協議。
而後就是分析具體的設備和場景是否適用,一頓腦補分析後,我看行(主要是WebRTC自己強大)。服務器
因爲具體業務和某些神祕的限定和權衡。採用TCP協議傳輸消息是不可能的。那沒啥好說的了,UDP救我!
雖然他們一次次給我帶來的都是,這個不行,那個也不行 的種種限制,可是好消息也是有的,在一系列複雜的操做後,我拿到了受控方模擬鼠標和鍵盤操做的接口。得,齊活!網絡
開發初期,筆者遇到的最大的問題就是UDP的傳輸是無鏈接的,不保證可靠性,可是在該業務場景下 多個受控端須要對比一段操做後的不一樣來上報異常,若是由於網絡的問題致使接收到的指令又略微不一樣,那 可用性就會大的下降 。性能
衆所周知,UDP的傳輸是不可靠的,它不像TCP那樣保證數據有序,不丟失的傳輸。可是限制就是在那裏,讓你頭大-.-。 那麼問題來了如何 在該場景下確保UDP可靠傳輸 ?優化
在通過一系列的腦補事後,個人就在 腦海裏產生了如下對話:spa
A:一個服務端控制多個客戶端,他們之間怎麼傳數據?
B:UDP組播?
A:業務容許丟包嗎?
B:不容許,並且還要有序執行。
複製代碼
A:發送端每發一條消息,接收端回服一條確認收到?
B:服務端一條消息發出去,而後接受幾十條確認消息?若是一秒發送30條數據呢?
A: 一臺客戶端經過組播發送丟包請求後,其餘客戶端接收到後就不發送相同丟包?
B:多臺客戶端同時丟包怎麼辦?隨機等待時間發送嗎?這樣作的話,延遲呢?不一樣步怎麼辦?並且等待的時間是否是還得監聽信道?
複製代碼
A:若是客戶端不能回覆給服務端肯定收到的消息,那就只能客戶端本身肯定丟包?
B:對,客戶端須要判斷本身丟包,而後反饋給服務端!
A:客戶端惟一的信息來源就是服務端以往發的包,因此就是客戶端發的包之間自帶聯繫。
B:客戶端發包的時候加上包的Index?而後客戶端根據上下倆個包Index是不是連續的來判斷是否丟包?
複製代碼
B: 好比如今服務器下發了包1,2,3後又下發4.
客戶端1收到了所有的消息,它保持沉默。
客戶端2丟失了信息3,當收到信息4時,它發現前一個包是信息2.因而它就會去給服務器發丟失信息3的反饋包?
A: 怎麼發?點對點仍是走組播?
B:走組播,若是其它接收端也丟了這個包,就能夠收到了。沒丟也能夠判斷Index來得知這是一個已執行過的包。
同時服務端須要維護一個緩衝區。須要清除已確認發送成功的數據,否則內存會爆。
A:可服務端沒辦法肯定接收端端包是否收到的啊?
B:若是丟包的機制生效。當客戶端反饋包9丟失,就意味着包9以前的包該客戶端都收到了。那就能夠把以前的包清除了。
固然也須要設置一個最大值。
A:但是存在多個發送端啊!其中一個收到後,服務端清除該包。一會後另外一臺客戶端又反饋丟了該包,怎麼辦?
B:和已經反饋過收到的客戶端要唄!
複製代碼
A:回到剛纔,客戶端收到信息2後緊接着收到信息4,判斷信息3丟了。發送信息3的再次傳送請求時,已接收到的信息4怎麼辦?
若是在等待信息3的過程當中,信息5,信息6,信息7都接受到了有如何處理?一直等着嗎?仍是一個倆個包就忽略丟失?
B:絕對不能忽略!由於客戶端不知道丟的包是什麼操做,若是是點擊,忽視會致使客戶端之間信息不一樣步很嚴重!因此只能等。
同事在收到包後,檢測是否有更大Index的包存在。若是有,強制執行該包。
複製代碼
B:這樣的話,全部的客戶端收到的消息都是同樣的,你們的包Index都是同一個。可是若是此時須要點對點通訊怎麼辦?
A:每一個客戶端都維護一套緩衝區?
B:不可能,成本太大。私聊走私聊通道,能夠不確保可靠性。
複製代碼
A:丟包以後等着回傳有可能致使延遲太大,多個客戶端不一樣步。
B:逆推,不須要等 --> 忽視丟包 --> 丟失包不重要 --> 你判斷出包不重要。
服務端發包時帶上過往的N個包的權重。這樣客戶端接受到包時就知道前面的包的權重了!
A:由於你是經過下一個包來判斷上一個包是否丟包的,因此若是下一個包攜帶以前N個包的重要程度。理論上就可能忽視丟包了!
B:因此,須要在發送端給包的權限分等級。好比,鼠標移動等級爲1,鼠標點擊爲10。
複製代碼
A:有一個需求將已段時間的操做存儲。而後隨時能夠一次性所有發送給接收端。
B:一次性發送大的數據會致使包在IP層被分片,包越大被分的片越多。該業務腳本會有多大?
A:和錄製時長成正比,並且用戶操做不可揣測,且不應被限定。因此咱們須要在服務端進行手動拆包在客戶端進行合包。
B:因此須要給拆分的包加上總包長,該包在總包中的Index等。
A:拆分過的包發送時依然遵循上述可靠協議發送,確保能夠收到全部的分包。
複製代碼
出於對發送信息不能太頻繁的考量,鼠標移動的過程當中 發送數據作了節流 。代價就是接收端的鼠標收到了一羣不連續的的位置點。因爲服務端是經過WebRTC顯示某一臺客戶端的畫面來進行操做的,因此用戶使用體驗不好。調試
若是服務端作了鼠標移動事件節流,那麼客戶端只須要作鼠標移動插值就好了。即在接受到的倆個鼠標移動事件中,本身計算中間值進行插值。實測優化效果很是明顯。code
在調試過程當中,發現客戶端在丟失數據包到接受到該丟失包的過程當中,會屢次的發送丟包信息給服務端。然後服務端又會在一段時間後返回多條數據(由於收到了多條丟包請求重傳的包).
通過一些實踐發現一旦發生丟包很容易是連續丟失。好比客戶端收到的信息Index序列是1,2,3,4,10. 此事客戶端認定包5丟失,等到包5被接受到,再去發送包6的丟失信息。如此往復,等到包9接受到時,其餘客戶端能夠已經接受到了包20,30甚至更大。這就會致使客戶端之間有長時間的不一樣步,這是該業務場景沒法接受的。