基於Tight VNC的遠程協助功能的實現


整體布局

  在遠程協助中,請求協助的一方稱為被協助方,或者(遠程協助)服務端;響應的一方稱為協助方,或者(遠程協助)客戶端;這里命名被協助方為Cv,而協助方命名為Cs,中轉服務器為Sr。

  TightVNC是免費為個人和商業用途,提供有用的,完整的源代碼, 在管理,技術支持,教育和許多其他用途, 跨平台,可用於Windows和Unix,Java客戶端,包括 與標准的VNC軟件兼容,符合RFB協議規范。

TightVNC 分為Server和 Viewer兩部分,Server作為請求協助端,Viewer協助端亦即是Cs和Cv 。

  Repeater為中轉服務器Sr,當Server連接Viewer是,通過Sr相互轉發數據。對Cs的監控端口為5500,Cv的監控端口為5900,當然5500、5900都是vnc的默認端口,這樣會引起其他一些VNC軟件的沖突,所以Cs和Cv監控端口都需要修改。

整個設備架設可簡化為如圖1所示,分為情況

1)     協助用戶Cv和請求協助用戶Cs1在同一局域網內,這時Cv與Cs1可以直接進行TCP通信;

2)     協助用戶Cv和請求協助用戶Cs2不在同一局域網內,這時Cv與Cs2只能通過中轉服務器相互轉發數據進行通信。

 

 

 整體布局

 

流程圖

一、被協助用戶Cs請求協助用戶Cv的協助過程流程圖如圖2所示;

 Cs請求Cv協助過程流程圖

1)     被協助者

  1. 用戶界面操作響應,向在線用戶發送1301請求(遠程協助請求)。
  2. 1301-Confirmation消息處理:

如果對方拒絕接受請求,顯示信息窗口,提示“遠程請求被對方拒絕”。

如果對方接受請求,則創建遠程協助對象;是否允許對方查看,調用遠程協助對象的StartServer,參數為對方用戶ID。

申請受控請求

2)     協助者

  1. 處理1301-Request消息。

窗口提示用戶“xxxx發出遠程協助邀請,您是否同意?”;如果“同意”則創建遠程協助對象,調用遠程協助對象Connect方法,連接對方的IP以及Port,建立TCP連接。調用用遠程協助對象的SetHost(),參數為用戶自己ID。

  1. 處理申請受控消息。

調用用遠程協助對象的SetViewOnly(),參數為false;遠程協助建立成功。

雙方通過中轉服務器或者直接建立TCP連接,完成遠程協助以及數據交互。

二、Server兩種請求協助方式Connect和Listen

1)     在局域網中Server端和 Viewer端如果能夠直接進行TCP通信的時,這時就不需要通過中轉服務器,Viewer直接連接Server。流程圖如圖3所示

 

 

Cv與Cs直接進行TCP通信流程圖

2)     當Server端和 Viewer端不能直接的TCP通信時,亦就可能是在兩個不同的局域網中,這時就需要通過中轉服務器Repeater進行Server端和 Viewer端的消息轉發。流程圖如圖4所示

 

Cv與Cs通過Repeater通信流程圖

遠程協助插件設計方案

將Server和 Viewer封裝在兩個動態庫中,分別為RemoteHelpServer和RemoteHelpViewer,外部調用RemoteHelpServer和RemoteHelpViewer時,應為單實例調用,即是已經作為一個客戶端或者是服務器端時,不能再與其他人進行遠程協助。

外部接口包括:連接方式、調節畫面質量、畫面模式和協助方式等

  1. 連接方式:Viewer直連Server和通過Repeater與Server通信;
  2. 調節畫面質量:1-9級別的lpeg圖像壓縮;
  3. 畫面模式:8位和24位畫面質量;
  4. 協助方式:僅屏幕查看和邀請方請求受控;
  5. 遠程協助窗體模式:最大化,還原和最小化;
  6. 快捷鍵:Shift+Ctrl+Alt+F 全屏、ESC退出全屏。

RemoteHelpServer和RemoteHelpViewer動態庫接口函數說明

Server端接口函數:

//參數:無

//返回值:Server端實例指針

//作用:創建遠程協助Server端實例

RemoteHelpHandle *CreateRemoteHelpServer();

 

//參數:Server端實例指針

//返回值:無

//作用:銷毀遠程協助Server端實例

void DestoryRemoteHelpServer(RemoteHelpHandle *handle);

 

//參數:Server端實例指針,中轉服務器IP及端口號

//返回值:成功時返回Socket指針

//作用:Server連接中轉服務器

SocketIPv4 *ServerConnect(RemoteHelpHandle *handle,TCHAR *host,unsigned short port);

 

//參數:Server端實例指針,監聽端口號

//返回值:成功時返回Socket指針

//作用:Server監聽

SocketIPv4 *ServerListen(RemoteHelpHandle *handle,unsigned short port);

 

//參數:Server端實例指針,Socket指針,是否作為服務監聽狀態

//返回值:0,1

//作用:Server確認Viewer可以查看

int AddNewConnection(RemoteHelpHandle *handle,SocketIPv4 *socket,bool bIsListenMode);

 

//參數:Server端實例指針,連接憑證

//返回值:無

//作用:Server連接憑證

void SetServerConnectionTag(RemoteHelpHandle *handle,char *pszConnectionTag);

 

//參數:Server端實例指針

//返回值:無

//作用:Server端斷開連接

void ServerDisconnect(RemoteHelpHandle *handle);

Viewer端接口函數:

//參數:無

//返回值:Viewer端實例指針

//作用:創建遠程協助Viewer端實例

RemoteHelpHandle *CreateRemoteHelpViewer();

 

//參數:Viewer端實例指針

//返回值:無

//作用:銷毀遠程協助Viewer端實例

void  DestoryRemoteHelpViewer(RemoteHelpHandle *handle);

 

//參數:Viewer端實例指針,中轉服務器IP及端口號

//返回值:關閉窗體時返回true,其它為false

//作用:Viewer連接中轉服務器

bool  ViewerConnect(RemoteHelpHandle *handle,char *host,int port);

 

//參數:Viewer端實例指針,中轉服務器IP及端口號

//返回值:關閉窗體時返回true,其它為false

//作用:Viewer連接中轉服務器

void  SetViewerConnectionTag(RemoteHelpHandle *handle,char *pszConnectionTag);

 

//參數:Viewer端實例指針,是否使用8Bit

//返回值:無

//作用:遠程協助圖像質量,兩種情況8Bit,24Bit

void  Set8BitColor(RemoteHelpHandle *handle,bool use8Bit);

 

//參數:Viewer端實例指針,壓縮率級數

//返回值:無

//作用:遠程協助圖像壓縮率

void setJpegCompressionLevel(RemoteHelpHandle *handle,int level);

 

//參數:Viewer端實例指針, 是否為只查看

//返回值:無

//作用:設置遠程協助只查看或可控

void setViewOnly(RemoteHelpHandle *handle,bool viewOnly);

 

//參數:Viewer端實例指針, 是否為全屏

//返回值:無

//作用:設置遠程協助窗體為全屏或退出全屏

void setFullScreenMode(RemoteHelpHandle *handle,bool fullScreenMode);

 

//參數:Viewer端實例指針

//返回值:無

//作用:Viewer端斷開連接

void ViewerDisconnect(RemoteHelpHandle *handle);

程序界面

一、遠程協助握手過程窗體界面顯示:

  請求遠程協助 

Viewer接受遠程協助,等待確認Server確認查看

Server確認查看

 Server已申請受控 

二、客戶端Cv:如圖5所示,連接成功后分別有屬性設置、最大化、斷開等按鈕;左下方為顯示圖像面板。

客戶端Cv

三、屬性設置:如圖6所示,調節畫面質量:1-9級別的lpeg圖像壓縮;畫面模式:8位和24位畫面質量

屬性設置

四、客戶端Cs:如圖7所示,連接成功后,可以斷開遠程控制或者停止受控等操作。

客戶端Cs

五、遠程協助窗體:窗體模式分別有最大化,還原和最小化;快捷鍵分別有Shift+Ctrl+Alt+F 全屏、ESC退出全屏。當窗體最大化即全屏時,頂部出現工具條,可以控制窗體最大化,還原和最小化等操作,如圖8所示,

 遠程協助全屏模式

 注: 

  在這個功能剛開始做的時候,google 、百度了很多這方面的資料,無奈資料還是比較少的。其中有一篇論文《基於P2P的遠程協助系統》,寫得比較詳細的方案,但最終沒有實現P2P的方法,可能是一開始就走了彎路和現成的系統限制了,時間方面不允許和穩定性。

  《基於P2P的遠程協助系統》里面寫是通過本地代理,中轉服務器建立橋梁實現遠程協助通信。其中本地代理可以不管VNC里面代碼的邏輯具體實現,只是負責通信,對遠程協助與VNC版本輕松解耦,沒有依賴VNC版本,可以不斷地使VNC更新換代有重要意義。還有一個作用是可以TCP打洞實現P2P《TCP實現P2P通信、TCP穿越NAT的方法、TCP打洞》,不依賴VNC內部發送規則。

  《TCP實現P2P通信、TCP穿越NAT的方法、TCP打洞》一文中這位前輩寫TCP實現P2P的方法,我想這對一些剛開始做TCP實現P2P通信有很大的幫助,感謝他的分享的精神,至少我對TCP實現P2P通信有所了解了。他所說的實現過程如下

  1、 S啟動兩個網絡偵聽,一個叫【主連接】偵聽,一個叫【協助打洞】的偵聽。
  2、 A和B分別與S的【主連接】保持聯系。
  3、 當A需要和B建立直接的TCP連接時,首先連接S的【協助打洞】端口,並發送協助連接申請。同時在該端口號上啟動偵聽。注意由於要在相同的網絡終端上綁定到不同的套接字上,所以必須為這些套接字設置 SO_REUSEADDR 屬性(即允許重用),否則偵聽會失敗。
  4、 S的【協助打洞】連接收到A的申請后通過【主連接】通知B,並將A經過NAT-A轉換后的公網IP地址和端口等信息告訴B。
  5、 B收到S的連接通知后首先與S的【協助打洞】端口連接,隨便發送一些數據后立即斷開,這樣做的目的是讓S能知道B經過NAT-B轉換后的公網IP和端口號。
  6、 B嘗試與A的經過NAT-A轉換后的公網IP地址和端口進行connect,根據不同的路由器會有不同的結果,有些路由器在這個操作就能建立連接(例如我用的TPLink R402),大多數路由器對於不請自到的SYN請求包直接丟棄而導致connect失敗,但NAT-A會紀錄此次連接的源地址和端口號,為接下來真正的連接做好了准備,這就是所謂的打洞,即B向A打了一個洞,下次A就能直接連接到B剛才使用的端口號了。
  7、 客戶端B打洞的同時在相同的端口上啟動偵聽。B在一切准備就緒以后通過與S的【主連接】回復消息“我已經准備好”,S在收到以后將B經過NAT-B轉換后的公網IP和端口號告訴給A。
  8、 A收到S回復的B的公網IP和端口號等信息以后,開始連接到B公網IP和端口號,由於在步驟6中B曾經嘗試連接過A的公網IP地址和端口,NAT-A紀錄了此次連接的信息,所以當A主動連接B時,NAT-B會認為是合法的SYN數據,並允許通過,從而直接的TCP連接建立起來了。

  整個過程跟UDP打洞很類似,我認為他文中寫可能是沒有注重的寫出一個重點,就是“Sock.SetSockOpt ( SO_REUSEADDR, &nOptValue , sizeof(UINT) )”其中的SO_REUSEADDR,因為這樣以上的過程才能夠實現。

  還有在OSChina里的一個討論貼“C++實現TCP打洞的思想”,有些人說的tcp打洞無法實現,那些人我認為是根本沒有去了解過吧,而且QQ的遠程協助是TCP連接的,難道QQ的遠程協助都是經服務器轉發的?

  知識有限,基本上想說的都說了!


  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM