1. 程式人生 > >RealVNC原始碼學習筆記 一

RealVNC原始碼學習筆記 一

RealVNC原始碼學習筆記 一

1VNC簡介

VNC是一款優秀的遠端控制軟體,其英文全拼為 Virtual NetworkComputerVNC 是在基於UNIXLinux作業系統的免費的開源軟體 目前VNC不僅僅支援UNIXLinux系統,其是一款跨平臺的開源軟體,處理支援UNIXLinux系統外,還支援WINDOWSMACSolarisHP-UX等作業系統。

VNC運用RFB協議,RFBremoteframe buffer 遠端幀快取的簡寫,是一個遠端圖形使用者的簡單協議,因為它工作在幀快取級別上,所以它可以應用於所有的視窗系統,例如:X11,Windows

Mac 系統。RFBRealVNC公司維護和更新,RFB協議定義了遠端圖形使用者客戶端與伺服器端的互動規則,如協商RFB協議版本、協商安全型別、協商畫素格式編碼方式等。RFB協議的具體內容可以參見RealVNC公司RFB協議文件

2VNC原始碼分析

windows系統的RealVNC的原始碼為基礎來分析VNCwindows平臺的具體

實現。vnc-4_1_3-winsrc目錄下有兩個目錄commonwin目錄,其中common目錄下放著各個平臺公用的模組,win目錄下放著為windows系統特的開發的模組。其目錄結構如下圖:

common目錄下的network

實現了對套接字的封裝;rdr實現了對輸入輸出IO操作的封裝,網路套接字的接收和傳送也由模組下的InstreamOutStream及其派生類實現的;rfb是非常重要的模組,VNC中大多機制的實現及訊息、事件的處理都是在該模組完成的;Xregion是為X11封裝的region類,在windows平臺上無需關注;zlib是一個壓縮模組,ZRLE編碼會使用到該模組;javabin模組為瀏覽器訪問VNC伺服器提供支援。

win目錄下的logmessages為訊息日誌模組;rfb_win32common目錄下rfb模組的windows化,該模組下的很多類與rfb下的類同名,或繼承自rfb下的類以實現某些類的

windows特例化;vncconfig為配置視窗及其功能的實現;vncviewer為客戶端的實現;winvnc為伺服器端的實現;wm_hooks為一起dll工程,其實現一個全域性hook,用來截獲系統的繪製等資訊。

RealVNC是用C++語言開發的,其中大量運用了名字空間,可以說其是對c++名稱空間運用的典範。如rdr下的所有類、全域性變數、函式等多包含在該rdr名稱空間下,在原始碼中可以發現很多using namespace rdrusing namespace rfbusing namespace win32的地方。RealVNCwindows原始碼運用了訊息和事件處驅動機制,有訊息、事件的到來、觸發來驅動VNC伺服器來完成各種任務。在RealVNC伺服器端主要可以分為兩條訊息、事件主線:windows訊息、網路套接字事件。後面將這兩個主線為基礎來分析RealVNCwindows原始碼。RealVNCwindows平臺上截圖的實現運用了hook技術。先來看一下wm_hook.dll

3wm_hook全域性鉤子dll的分析

wm_hook中安裝了攔截系統SendMessagePostMessage訊息的鉤子,但對攔截到的訊息並不做處理,而是將其轉化為自定義的系統訊息並將轉換得到的訊息傳送給指定的執行緒,由該執行緒對相應的訊息做出處理。

wm_hookwindows APIRegisterWindowMessage函式註冊了自定義訊息,這些訊息反映了桌面的改變情況,如UINTWM_HK_WindowClientAreaChanged =RegisterWindowMessage(_T("RFB.WM_Hooks.WindowClientAreaChanged"))等。接下來,來看一下hook dll的處理過程。

首先在WM_Hooks_Install(DWORDowner, DWORD thread)函式中安裝了四類hook,並未四類hook分別設定了回撥函式,在各個回撥函式中呼叫ProcessWindowMessage(UINTmsg, HWND wnd, WPARAM wParam, LPARAM lParam)函式對截獲的系統訊息進行處理,在ProcessWindowMessage函式中根據訊息的型別呼叫不同的函式將對應的自定義訊息傳送給hook的擁有者執行緒,當擁有者執行緒得到訊息進行處理(獲得更新的區域,在接到客戶端訊息後就將更新區域的桌面影象編碼傳送給客戶端)

4wm_hook全域性dll的呼叫

上層應用通過WHooks.cxx中的全域性靜態函式StartHookThread()載入wm_hook.dll,並建立開始hook執行緒

static bool StartHookThread() {

//WMHooksThread* hook_mgr

//執行緒已開啟返回true

if (hook_mgr)

return true;

vlog.debug("creatingthread");

//建立一個新的hook執行緒 WMHooksThread繼承自Thread在此建構函式中會載入wm_hook.dll

hook_mgr = new WMHooksThread();

//判斷wm_hook.dll是否載入成功

if(!hook_mgr->WM_Hooks_Install.isValid() ||

!hook_mgr->WM_Hooks_Remove.isValid()) {

vlog.debug("hooks notavailable");

return false;

}

vlog.debug("installinghooks");

//安裝wm_hook 監視桌面的所有執行緒 第二個引數為0 表示監視一切執行緒,在該函式中呼叫了//SetWindowsHookEx函式安裝了四類hook

if(!(*hook_mgr->WM_Hooks_Install)(hook_mgr->getThreadId(), 0)) {

vlog.error("failed toinitialise hooks");

delete hook_mgr->join();

hook_mgr = 0;

return false;

}

vlog.debug("startingthread");

//開始執行緒執行WMHooksThread類的run函式

hook_mgr->start();

return true;

}

其中hook_mgrWHooks.cxx中定義的WMHooksThread*全域性變數,WMHooksThread的父類Threadrfb_win32模組實現的一個執行緒基類。該基類建立的執行緒是一個狀態為SUSPEND的懸掛執行緒,執行緒建立後並不立即執行,而是懸掛在那裡,只到呼叫ResumeThread函式後,執行緒才開始執行,ResumeThread函式的呼叫在Thread類的start()成員函式中。因此在構造執行緒類之後,只有呼叫了該類族的start()函式,該執行緒才會啟動,線上程函式中將會呼叫該類的run函式,也就是執行緒啟動後將呼叫執行緒類的run函式。下面來看一下WMHooksThread類的run函式:

//執行緒函式,線上程函式中響應底層hook.dll截獲轉發的訊息

void

WMHooksThread::run() {

//DynamicFn是一個模板類,用來獲取指定dll中指定函式名的函式的指標

DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowChanged(_T("wm_hooks.dll"),"WM_Hooks_WindowChanged");

DynamicFn<WM_Hooks_WMVAL_proto>WM_Hooks_WindowBorderChanged(_T("wm_hooks.dll"),"WM_Hooks_WindowBorderChanged");

DynamicFn<WM_Hooks_WMVAL_proto>WM_Hooks_WindowClientAreaChanged(_T("wm_hooks.dll"),"WM_Hooks_WindowClientAreaChanged");

DynamicFn<WM_Hooks_WMVAL_proto>WM_Hooks_RectangleChanged(_T("wm_hooks.dll"),"WM_Hooks_RectangleChanged");

DynamicFn<WM_Hooks_WMVAL_proto>WM_Hooks_CursorChanged(_T("wm_hooks.dll"),"WM_Hooks_CursorChanged");

//獲得dll中所註冊的訊息ID GetMsgVal中呼叫以上獲得的函式指標,獲得對應的註冊的訊息//的ID

UINT windowMsg =GetMsgVal(WM_Hooks_WindowChanged);

UINT clientAreaMsg =GetMsgVal(WM_Hooks_WindowClientAreaChanged);

UINT borderMsg =GetMsgVal(WM_Hooks_WindowBorderChanged);

UINT rectangleMsg =GetMsgVal(WM_Hooks_RectangleChanged);

UINT cursorMsg =GetMsgVal(WM_Hooks_CursorChanged);

#ifdef _DEBUG

DynamicFn<WM_Hooks_WMVAL_proto>WM_Hooks_Diagnostic(_T("wm_hooks.dll"),"WM_Hooks_Diagnostic");

UINT diagnosticMsg =GetMsgVal(WM_Hooks_Diagnostic);

#endif

//變數定義

MSG msg;

RECT wrect;

HWND hwnd;

int count = 0;

// Update delay handling

//We delay updates by 40-80ms, so that thetriggering application has time to

//actually complete them before we notify thehook callbacks & they go off

//capturing screen state.

const int updateDelayMs = 40;

//建立一個視窗

MsgWindowupdateDelayWnd(_T("WMHooks::updateDelay"));

//建立一個定時器,IntervalTimer類中呼叫SetTimer實現定時

IntervalTimerupdateDelayTimer(updateDelayWnd.getHandle(), 1);

//顯示區域

Region updates[2];

int activeRgn = 0;

vlog.debug("starting hookthread");

//響應hook.dll中傳送的訊息 呼叫GetMessage來獲得訊息

while (active &&GetMessage(&msg, NULL, 0, 0)) {

count++;

//定時器訊息,該訊息有IntervalTimer類定時到時發出

if (msg.message == WM_TIMER) {

// Actually notify callbacksof graphical updates

//通知hook類,將獲取的新區域新增到更新區域中,並觸發hook類的事件物件為有訊號

NotifyHooksRegion(updates[1-activeRgn]);

if(updates[activeRgn].is_empty())

updateDelayTimer.stop();

activeRgn = 1-activeRgn;

updates[activeRgn].clear();

} else if (msg.message ==windowMsg) {

// An entire window has(potentially) changed

hwnd = (HWND) msg.lParam;

if (IsWindow(hwnd) &&IsWindowVisible(hwnd) && !IsIconic(hwnd) &&

GetWindowRect(hwnd,&wrect) && !IsRectEmpty(&wrect)) {

//在此獲得視窗區域,並開始定時,定時到時將此區域加入到改變的區域中

updates[activeRgn].assign_union(Rect(wrect.left,wrect.top,

wrect.right, wrect.bottom));

updateDelayTimer.start(updateDelayMs);

}

} else if (msg.message ==clientAreaMsg) {

// The client area of a windowhas (potentially) changed

hwnd = (HWND) msg.lParam;

/在此獲得客戶區域,並開始定時,定時到時將此區域加入到改變的區域中

if (IsWindow(hwnd) &&IsWindowVisible(hwnd) && !IsIconic(hwnd) &&

GetClientRect(hwnd,&wrect) && !IsRectEmpty(&wrect))

{

POINT pt = {0,0};

//進行座標轉換,將客戶區座標轉換為全屏座標

if (ClientToScreen(hwnd,&pt)) {

updates[activeRgn].assign_union(Rect(wrect.left+pt.x, wrect.top+pt.y,

wrect.right+pt.x, wrect.bottom+pt.y));

updateDelayTimer.start(updateDelayMs);

}

}

} else if (msg.message ==borderMsg) {

hwnd = (HWND) msg.lParam;

if (IsWindow(hwnd) &&IsWindowVisible(hwnd) && !IsIconic(hwnd) &&

GetWindowRect(hwnd,&wrect) && !IsRectEmpty(&wrect))

{

Regionchanged(Rect(wrect.left, wrect.top, wrect.right, wrect.bottom));

RECT crect;

POINT pt = {0,0};

if (GetClientRect(hwnd,&crect) && ClientToScreen(hwnd, &pt) &&

!IsRectEmpty(&crect))

{

changed.assign_subtract(Rect(crect.left+pt.x, crect.top+pt.y,

crect.right+pt.x, crect.bottom+pt.y));

}

if (!changed.is_empty()) {

updates[activeRgn].assign_union(changed);

updateDelayTimer.start(updateDelayMs);

}

}

} else if (msg.message ==rectangleMsg) {

Rect r = Rect(LOWORD(msg.wParam),HIWORD(msg.wParam),

LOWORD(msg.lParam), HIWORD(msg.lParam));

if (!r.is_empty()) {

updates[activeRgn].assign_union(r);

updateDelayTimer.start(updateDelayMs);

}

} else if (msg.message == cursorMsg){

NotifyHooksCursor((HCURSOR)msg.lParam);

#ifdef _DEBUG

} else if (msg.message ==diagnosticMsg) {

vlog.info("DIAGmsg=%x(%d) wnd=%lx", msg.wParam, msg.wParam, msg.lParam);

#endif

}

}

vlog.debug("stopping hookthread - processed %d events", count);

(*WM_Hooks_Remove)(getThreadId());

}

在該run函式中利用while迴圈處理wm_hook.dll傳送的各種訊息其中while迴圈中的if語句中對訊息的判斷值都是wm_hook.dll中註冊的訊息ID。這裡需要重點理解的是while迴圈中第一個if語句中的NotifyHooksRegion(updates[1-activeRgn]),該函式遍歷hook物件佇列,並呼叫佇列中每一個hook物件的NotifyHooksRegion函式,來新增更新區域和觸發事件物件:

//hooksWHooks.cxx中定義的WMHooks類的物件佇列std::list<WMHooks*> hooks;

//std::list<WMCursorHooks*> cursor_hooks; 滑鼠hook

//Mutex hook_mgr_lock;

static void NotifyHooksRegion(const Region& r) {

Lock l(hook_mgr_lock);

std::list<WMHooks*>::iteratori;

for (i=hooks.begin();i!=hooks.end(); i++)

(*i)->NotifyHooksRegion(r);

}

void rfb::win32::WMHooks::NotifyHooksRegion(const Region& r) {

// hook_mgr_lock is already heldat this point

updates.add_changed(r);

updatesReady = true;

SetEvent(updateEvent);

}

通過上面的介紹可以wm_hook.dll訊息到來,程式將桌面改變區域儲存起來,觸發hook類的事件物件。這樣程式就將訊息的處理轉化為了對事件的處理。所有的hooks物件共用一個hook執行緒。下面再來看下WMHook類,WHooks的定義在rfb_win32模組的WMHooks.h檔案中,該檔案定義了WMHooksWMCursorHooks,來支援桌面改變hook和滑鼠改變hook。進一步再來看一下這些hook類的例項化。

5WMHooksWMCursorHooks的例項化

WHooks類被封裝到了SDisplayCoreWMHooks類中,在SDisplayCoreWMHooks中定義了一個WHooks變數,並在SDisplayCoreWMHooks的初始化過程中將SDisplay類例項物件的UpdatEvent事件傳給WHooks物件,這樣一來觸發WHooks物件的事件物件,就是觸發了SDisplay類的事件物件,也就是說,每當wm_hook.dll截獲有關桌面變化的訊息,就會觸發SDisplay類物件的事件物件updateEvvent,該變數由API CreateEvent函式建立。接下來分析一下SDisplay的作用。

SDisplay類在rfb_win32模組中實現,該類對系統顯示進行了封裝。該類的功能相當強大,這裡只關注其對updateEvvent事件的處理。在SDisplayprocessEvent(HANDLE event)函式中實現對事件的處理,在該函式中core->flushUpdates()語句將WMHooks物件中儲存的更新區域傳給SDisplayupdates變數。到此為止,桌面更新的區域已經儲存到了SDisplayupdates變數中,獲取、編碼、傳送updates區域的桌面影象,就是處理了桌面更新變化區域。Updates變數是SimpleUpdateTracker物件,SimpleUpdateTracker類在rfb模組中實現,該類就是用來實現對桌面更新區域的具體操作。