整體布局
在遠程協助中,請求協助的一方稱為被協助方,或者(遠程協助)服務端;響應的一方稱為協助方,或者(遠程協助)客戶端;這里命名被協助方為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) 被協助者
- 用戶界面操作響應,向在線用戶發送1301請求(遠程協助請求)。
- 1301-Confirmation消息處理:
如果對方拒絕接受請求,顯示信息窗口,提示“遠程請求被對方拒絕”。
如果對方接受請求,則創建遠程協助對象;是否允許對方查看,調用遠程協助對象的StartServer,參數為對方用戶ID。
申請受控請求
2) 協助者
- 處理1301-Request消息。
窗口提示用戶“xxxx發出遠程協助邀請,您是否同意?”;如果“同意”則創建遠程協助對象,調用遠程協助對象Connect方法,連接對方的IP以及Port,建立TCP連接。調用用遠程協助對象的SetHost(),參數為用戶自己ID。
- 處理申請受控消息。
調用用遠程協助對象的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時,應為單實例調用,即是已經作為一個客戶端或者是服務器端時,不能再與其他人進行遠程協助。
外部接口包括:連接方式、調節畫面質量、畫面模式和協助方式等
- 連接方式:Viewer直連Server和通過Repeater與Server通信;
- 調節畫面質量:1-9級別的lpeg圖像壓縮;
- 畫面模式:8位和24位畫面質量;
- 協助方式:僅屏幕查看和邀請方請求受控;
- 遠程協助窗體模式:最大化,還原和最小化;
- 快捷鍵: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的遠程協助都是經服務器轉發的?
知識有限,基本上想說的都說了!