阅读 809

一种基于WebRTC与UDP组播的一对多远程控制桌面的实现思路

  • 苏格团队
  • 作者:Jason

前言

笔者最近收到要求 远程控制局域网内多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 客户端短时间内反复发送丢包导致服务端负载大

在调试过程中,发现客户端在丢失数据包到接受到该丢失包的过程中,会多次的发送丢包信息给服务端。而后服务端又会在一段时间后返回多条数据(因为收到了多条丢包请求重传的包).

解决思路:为了造成不必要的性能损失,我们对发送同一个包的丢包请求做了节流。

问题3 连续丢包导致客户端卡顿

经过一些实践发现一旦发生丢包很容易是连续丢失。比如客户端收到的信息Index序列是1,2,3,4,10. 此事客户端认定包5丢失,等到包5被接受到,再去发送包6的丢失信息。如此往复,等到包9接受到时,其他客户端可以已经接受到了包20,30甚至更大。这就会导致客户端之间有长时间的不同步,这是该业务场景无法接受的。

解决思路:客户端一次说明所有丢包,服务端接受后一次性返回。