Windows远程桌面实现之四(在现代浏览器中通过普通页面访问远程桌面)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fanxiushu/article/details/78869719

                                        by Fanxiushu 2017-12-21   转载或引用请注明原始作者。


前几篇文章分别阐述了如何抓取windows桌面图像,以及相关摄像头,电脑内部声音等采集,相关连接如下:
http://blog.csdn.net/fanxiushu/article/details/73269286  (抓屏技术总览 MirrorDriver,DXGI,GDI)
http://blog.csdn.net/fanxiushu/article/details/76039801  (抓屏技术之MirrorDriver镜像驱动开发)
http://blog.csdn.net/fanxiushu/article/details/77013158  (电脑内部声音采集,录音采集,摄像头视频采集)

这篇文章描述如何在浏览器中,直接打开网页方式而且不需要任何插件就可以浏览远程桌面,同时进行鼠标键盘等控制。

记得很早前,当初热衷于远程桌面图像技术,就一直想着在浏览器中直接浏览远程桌面图像,同时进行控制,因为这样感觉很酷。
奈何当时并没有HTML5技术,而且也并没有WebSocket等技术出现,要在浏览器中达到这种效果,就只能以插件方式实现这种功能。
比如ActiveX控件,或者java的applet插件等,相当于还是得在客户端实现一个原生程序,原生程序以插件方式嵌入到浏览器中。
这跟直接做一个客户端程序没啥区别,而且要兼容各种浏览器就得实现每种浏览器的插件接口。
随着这些年互联网技术的发展,HTML5标准的出现,WebSocket在网页开发技术中的应用,使得直接在各个浏览器中,
通过JS脚本的开发,就能轻松的实现在普通页面浏览和控制远程桌面。
这给我们带来什么好处?首先就是因为不需要插件,能在绝大部分现代浏览器中正常运行,包括各种移动设备。
目前的现状与以前不同,以前的终端设备大部分是PC电脑为主,现在各种手机以及其他移动设备,当然还包括平板,笔记本,PC台式机等。
各种各样的终端设备,如果都要开发原生程序,得做好几套程序。
如果你为了省事,而且也不需要深度使用远程桌面功能,就在浏览器中使用也未尝不是一个好选择。
当然为了获得更好的体验效果,还是得开发原生的客户端远程桌面程序,因为不论是解压图像时候CPU占用率,还是画质效果以及起他功能,
都不是浏览器中的远程桌面能与之比拟的。

这篇文章讲述如何通过JS脚本开发,通过WebSocket网络通讯,HTML5标准的canvas绘画,
WebAudio声音来展现一个完整的远程桌面效果。
其中图像和音频的javascript解压缩利用开源库h264bsd和jsmpeg。
因为我不是做JS脚本和HTML5网页的开发工程师,只是因为想要在浏览器中实现远程桌面功能,才开始接触这些方面的内容。
因此文章有何不妥之处,请指出。

我们先来了解普通远程桌面客户端的开发过程。
首先得制定一套通讯协议,用来传输桌面图像数据,电脑内部的音频数据,鼠标键盘等控制信息数据。
RDP协议,VNC协议,SPICE协议等,这些都是公开使用的通讯协议,基本上是建立在TCP通讯之上。
比如windows的远程桌面使用的RDP协议,我们只有在各种客户端操作系统中,
开发出符合RDP协议的程序,就能在各种客户端远程控制windows系统平台。
类似RDP产品很多,基本上Android,iOS,MacOS等操作系统中都有对应的RDP软件。
而像VNC,则是类似Linux,MacOS等系统常用的远程控制协议。
这里并不打算介绍这类公开通讯协议,而是使用我们自己建立在TCP上的私有协议进行通讯。
远程桌面通讯协议有个最大的特点,就是基本上都是基于一个一个的数据包进行数据传输,包括数据包的推送和拉取。
而且是建立在TCP这种流套接字的基于数据包方式的通讯,这跟我在如下的文章介绍的TCP工作方式是一致的。
http://blog.csdn.net/fanxiushu/article/details/50631626  (基于TCP流协议的数据包通讯)
因此我们设计的私有协议也应具备类似的工作方式的。
这种一个一个数据包传输方式跟HTTP协议的请求-应答模式有很大差别。
这就是为什么在 WebSocket 没出现前,要在浏览器使用js解决远程桌面的通讯问题是个很大的麻烦,很多时候必须依靠插件才能完成。

当制定好一套私有通讯协议,被控制端根据通讯协议格式传输经过编码压缩的图像和音频的一个一个数据包到控制端(客户端)。
客户端接收到一个一个的图像数据包,根据每个数据包的指示,采用不同的解码方式对图像或音频解码。
解码之后,就是原始的YUV或者RGB图像数据,然后使用画图函数把图像画到窗口中,从而就显示出远程被控制端的桌面图像。
再把解码之后的音频数据送到播放设备播放。
这就是一个远程客户端的开发大致流程。其实,我们完全可以把远程桌面理解成直播系统,被控制端相当于直播的推流。
只是这个直播系统要求的实时性非常高,不能超过一秒(估计一秒的延时都够呛),
否则当使用鼠标键盘控制远程系统的时候,那种延迟基本是不能接受的。

再看看浏览器中的实现,WebSocket的出现,帮我们解决了远程桌面通讯协议的麻烦,
而图像最终要显示到窗口中,HTML5的canvas的出现,可以在canvas上边画图,又帮我们解决了图像显示的问题。
HTML5的WebAudio的出现,帮我们解决了在浏览器中播放音频数据块的问题。
因此在现代的浏览器中增加了这些基本内容,就完全帮我们解决了以前只有插件才能做到的事情。

我们使用WebSocket连接到被控服务端获取数据。在js中调用
var ws = new WebSocket("ws://www.dns.com/wsock_stream");
就创建一个到 www.dns.com主机的WebSocket连接,请求的uri是 /wsock_stream 。
然后
ws.onclose=function(e){ ...}   // WebSocket 关闭
ws.onerror=function(e){ ... }   // WebSocket 连接出错
ws.onopen=function(e){...}     // WebSocket 成功建立了连接
ws.onmessage=function(e){....} //接收到数据包
ws.send(...)  // 发送数据包。
看起来够简单吧,比起 c++中使用BSD Socket简单多了。
再看看WebSocket服务端,服务端不是使用现成比如node.js做开发,
而是用C/C++语言直接BSD Socket来解析处理WebSocket协议,以及解析处理HTTP协议。
因为从镜像驱动采集桌面图像,到图像转换编码压缩,到网络传输,清一色都是使用此语言实现的。
在其中嵌入网页方式的远程桌面只是其中一个附加功能。自然,实现的是简单的嵌入式Web服务端。
至于程序,稍后可以到GITHUB或者CSDN下载下来玩玩。

C++中实现WebSocket,我们就得熟悉WebSocket协议格式,
WebSocket首先发送一个跟HTTP协议格式一样的数据包(HTTP请求)到服务端,服务端回复 101 的HTTP应答,
之后这个连接切换到WebSocket通讯。WebSocket实际上是基于TCP上的数据包方式的通讯模式。
WebSocket头一个字节包含操作码等信息,接下来一个字节是数据包长度,这个长度表示方式比较特别,
如果数据长度<126,这个字节就代表实际的数据长度,如果  126<=数据长度 < 65536 ,这个字节固定126,接下两个字节表示真实长度。
如果数据长度超过 65536 这个字节固定127,接下来 8个字节代表真实数据长度。
更详细的WebSocket协议说明,请查看 RFC文档,相对而言不复杂,可以使用C++封装成类似 js 那样的函数调用方式。

接下来还得在js端解决WebSocket断线重连问题,让网络连接能自动恢复。
我们可以在 onerror, onclose,以及异常里边重新创建 WebSocket连接,
同时调用 setTimout 定时发送心跳包来保持连接,具体可查看稍后发布到 CSDN或GITHUB上的 javascript代码。

然后就是我们的主题:在WebSocket里承载我们的私有协议数据包,用来传递图像数据,音频数据,以及鼠标键盘等控制数据。
我们这里使用每个数据包都是用一个公共的头,然后接下来是对应的数据。
这个公共的头如下定义数据结构:
struct net_header_t
{
    unsigned char   cmd; // 0, noop, 1 login,  2 RECTs, 3 audio AAC, 4 Mouse input, 5 keyboard input, 6 param set, 7 display change , 8 cursor
    unsigned char   subcmd; // when cmd=0 1 request, 2 reply; when cmd=2, subcmd=all data compress methed

    /////
    union {
        login_t           login;//登录信息

        display_change_t  display_change;//屏幕大小改变
       
        image_rect_t      image_rect;//图像数据
       
        audio_sample_t    audio_sample;//音频数据
       
        kbd_event_t       kbd_event;//键盘事件
       
        mouse_event_t     mouse_event;//鼠标事件

        cursor_t          cursor;//鼠标形状

        proxy_t           proxy; // 通过服务端中转数据结构

        param_t           param; // 设置获取参数
        /////

    };

};
这样每个数据包,都包含 net_header_t 头,
其中image_rect_t 代表的是图像数据,在cmd=2,使用image_rect_t这个结构。
audio_sample_t代表的是音频数据头,cmd=3时候使用。
当代表图像数据时候,接下来包含至少一个或者多个 ( image_rect_header_t 头 + 压缩的图像数据 )。
这样可以在一个数据包里同时传递多个变化的图像数据,这是在当初设计时候,需要传输远程桌面变化的矩形框区域图像。
然后我们在websocket的onmessage回调函数中接收到数据包,这个数据包可能是图像包,也可能是音频包或者其他,
因为网页端能找到开源的javascript解码源代码非常有限,目前只找到h264bsd和jsmpeg解码,
而自己并不擅长做图像编解码更不擅长用js解码,因此发送到网页端的只是编码算法中的H264和MPEG1,音频是MP2.
具体如何使用jsmpeg和h264bsd可以查看它们的example或者查看稍后发布到CSDN或GITHUB的这部分javascript代码。

javascript解码其实就是利用CPU运算的软解码。
即使直接C语言+汇编开发的图像软解码程序解码类似H264这样的算法,消耗的CPU都非常高。
就更别提使用js脚本来解码了。在现代的PC电脑中的CPU利用js解码还能凑合使用,到了移动平台比如手机,CPU基本就不在一个层次了。
比如我的iPhone6手机,在iOS自带浏览器中远程桌面1920X1080的图像,如果远程桌面不是经常变化,凑合还能接受,
如果在远程桌面播放视频,在iPhone6手机里基本不能正常运行,MPEG1和H264都不行,也许你的最新的手机CPU能扛得住。
(下面会提到使用MJPG方式来实现流畅播放。)
不过如果换成是PC(最近两三年新的CPU)的浏览器,chrome或者firefox或者opera或者edge等,(IE对HTM5支持并不好,排除在外)
即使1920X1080远程桌面中播放视频也比较流畅。

使用javascript解码图像前,其实想了许久,想了多种其他在网页中显示图像的解决办法。
前面说过了,远程桌面可以理解成实时性要求很高的直播系统。
一开始想到的是HLS,因为它协议够简单,能使用普通HTTP协议承载视频,而且是利用HTML5的video标签播放,
利用video标签意味着浏览器内部是使用硬件解码加速,比起js纯软解码效率高了许多,尤其是在手机这样的移动平台,
利用video标签流畅播放1080P视频都不是问题。
但是仔细去分析HLS协议,会发现延时非常高,动不动就几十秒的延时,无论如何也做不到1秒内的延时,这对远程桌面是不可接受的。
因此只能放弃这种方案。
然后就是RTMP协议,还有flv直播,可惜这都需要依赖Flash控件,在电脑端使用Flash没啥问题,到了移动端就没辙了。
当初要实现网页方式控制,主要目的就是为了各自平台都能使用,而且不用开发各种平台的客户端程序。
想来想去,在各种平台都兼容的网页中实现高实时性直播,是个蛋疼的问题。
使用HTML5的video标签来解决直播问题更是蛋疼。因此只好考虑在HTML5中利用js解码来到达既能兼容各种平台,又能保证高实时性。

最后简单说说MJPG。
在以前提供的桌面图像采集源代码中,简单使用了MJPG流在浏览器中展现远程桌面,
下载连接如下:
http://download.csdn.net/download/fanxiushu/9910360  (windows平台抓屏源代码下载)
本来后来的开发中,打算放弃MJPG这种方案,因为占用网络带宽太高,尤其是在远程桌面播放视频的时候。
但是利用了H264和MPEG1的js软解码,结果在手机平台挺糟糕,因此只好重新完善MJPG流方式。

利用img标签来显示MJPG流,这个在chrome,firefox,opera,Microsoft Edge,以及Safari(苹果iOS和MacOS自带浏览器)
都能支持MJPG流,但是IE不支持(当然以IE为内核的也不支持)。
这里说的MJPG流,其实就是一张一张的JPG图片,只是在HTTP应答头中 的Content-Type设置为 multipart/x-mixed-replace类型。
然后就循环不停的一段一段的传输JPG图片。MJPG流的C++实现代码可从上边的连接下载,其实是挺简单的。
因此我们只需要简单的在网页端 <img src="URL" /> 这样就能展现MJPG流。
然后再结合WebSocket传输音频数据和鼠标键盘数据,也就是图像流使用HTTP协议传输,其他数据使用WebSocket传输。
这里有个问题就是断线重连问题,img标签没法实现断线重连。因此想了一个办法,在服务端生成一个唯一ID,
同时传给WebSocket作为URL参数和MJPG流的URL参数,然后在WebSocket中定时发送查询命令到服务端检测MJPG的这个唯一ID。
如果不存在,说明已经断线了,这个时候把
img标签重新刷新。详细实现可查看稍后发布的js代码。

GITHUB上下程序载地址:
https://github.com/fanxiushu/xdisp_virt
CSDN上下载地址

http://download.csdn.net/download/fanxiushu/10168823

程序一共两个,
1, xdisp_virt.exe 这个是核心程序,负责抓屏,编码压缩,本身也提供给浏览器连接和原始客户端程序连接,同时也提供连接到
                           xdisp_server服务端,
2, xdisp_server.exe 这个是中转服务端程序,就是当许多机器处于复杂局域网环境时候,可以把xdisp_server部署到公网上,
                                然后让所有xdisp_virt.exe程序连接到这个服务端,这就等于是xdisp_server管理和中转了所有xdisp_virt机器。

这是开发中的一个版本,计划有点大,
比如还没实现远程文件传输功能,复制粘贴也需要实现。
打算实现windows桌面扩展功能,类似iDisplay软件,这个需要开发WDDM驱动,正好借此机会熟练WDDM驱动。
手机端还得实现原生APP来采集手机屏幕和相机,然后连接到xdisp_server程序。这样桌面采集不单局限于windows平台了
然而计划跟不上变化,到时能做到什么程度,看当时的心情和是否有利可图了,
因为有些东西纯粹就是一大堆无聊代码,做起来又浪费时间和精力,个人精力有限。

下面展现的是开发的这个程序在浏览器的一些图片效果

下图是用浏览器登录到xdisp_server中转服务端页面图(当然也没挺简单)


下图是选择一个连接后出现的三种图像编码方式:

下图显示远程桌面(这张图尺寸太大 可能会看不清,远程控制的是 2560X1600 的电脑屏幕):

 
下图是iPhone手机上系统自带浏览器效果:

阅读更多

没有更多推荐了,返回首页