Windows遠程桌面實現之四(在現代瀏覽器中通過普通頁面訪問遠程桌面)

                                        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手機上系統自帶瀏覽器效果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章