RealVNC源码学习笔记 一

来源:互联网 发布:java调用ffmpeg命令行 编辑:程序博客网 时间:2024/05/16 05:43

RealVNC源码学习笔记 一

1VNC简介

VNC是一款优秀的远程控制软件,其英文全拼为 Virtual NetworkComputerVNC是在基于 UNIX  Linux 操作系统的免费的开源软件 目前VNC不仅仅支持UNIXLinux系统,其是一款跨平台的开源软件,处理支持UNIXLinux系统外,还支持WINDOWSMACSolarisHP-UX等操作系统。

目前比较著名的VNC软件有RealVNCTightVNCRealVNC的官方网址为http://www.realvnc.comTightVNC的官方网址为http://www.tightvnc.com

 VNC运用RFB协议,RFBremoteframe buffer 远程帧缓存的简写,是一个远程图形用户的简单协议,因为它工作在帧缓存级别上,所以它可以应用于所有的窗口系统,例如:X11,Windows Mac系统。RFBRealVNC公司维护和更新,RFB协议定义了远程图形用户客户端与服务器端的交互规则,如协商RFB协议版本、协商安全类型、协商像素格式编码方式等。RFB协议的具体内容可以参见RealVNC公司RFB协议文档http://tools.ietf.org/pdf/rfc6143.pdf

 

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模块中实现,该类就是用来实现对桌面更新区域的具体操作。

0 0