TightVNC 源码学习

1. 首先得讲一下它用到的omni_thread库

google搜出来的链接:http://omniorb.sourceforge.net/omni40/omnithread.html,介绍
了omni_thread库的基本使用,足够了,这里还是把它用中文啰嗦一下:

1) 它的接口与用C语言实现的POSIX threads接口是类似的。作为线程库,它提供了基本的
 同步对象:互斥量,条件变量,信号量;还提供了线程对象;也提供了线程私有数据。

2) 互斥量(Mutex)
 omni_mutex对象, 操作它的方法有lock(),unlock()。acquire()和release()分别与
 lock(), unlock()有相同的语义。

3) 条件变量(Conditional variable)
 omni_condition对象。wait()方法使线程在条件变量上等待,signal()唤醒至少一个正等
 待该条件变量的线程(pthread_cond_wait()是只唤醒一个正等待的线程,不知道是不是这
 个 文档写错了,竟然与pthread语义不一样),broadcast()唤醒所有正等待该条件变量
 的线程。
 一旦omni_condition对象创建,一个互斥量就与该条件变量对象在对象的整个生命周期
 中绑定,而pthread的条件变量只在wait期间进行绑定。

4) 信号量(semaphore)
 omni_semaphore对象。wait()等待,即Dijkstra所说的P操作;post()唤醒,即V操作。

5) 线程对象(thread object)
 有两种方法使用线程对象。第一种,以需要执行的函数为参数创建对象。第二种,从omni
 _thread继承产生一个新类,omni_thread库将会执行该新类的run()或run_undetached()
 函 数 。
 一旦线程对象创建,即可调用start()或start_undetached()使线程开始运行。若调用sta
 rt()函数,omni_thread库会以run()函数为执行例程,并detached模式运行。若调用
 start_undetached()函数,omni_thread库会以run_undetached()函数为执行例程,但以u
 ndetached模式运行。

6) 线程私有数据(per-thread data)
 略。
 

2. RFB协议
 google找出来的协议文档链接:http://www.realvnc.com/docs/rfbproto.pdf,十分简单
 的协议。它的基本原则是:瘦客户端,客户端拉模式,只传输屏幕变化的区域,每一次的
 更新都是由一系列的矩形区域构成。与RDP实现方法完成不同。
 
3. Hook
 一般翻译成挂钩,不过个人觉得某本书上翻译的异常分支比较直观一点。Tight VNC源码
 中提供的Hook源码比较简单,不用多讲。只是有一点要注意,Hook无法得到所有屏幕变化
 的区域,最终还是得靠全屏幕扫描来得到所有的屏幕变化区域。
 另外,Ultra VNC源码中 提供的Hook源码用PostThreadMessage函数来发送消息,我用这
 份Hook源码作测试,除了调用SetWindowsHook函数的进程外,其他的进程根本收不任何
 消息。MSDN讲该函数往GUI 线程发消息容易丢失,但我是发送到普通线程,并非GUI
 线 程 。用Process Viewer查看,确实每个进程都把DLL映射到自己的地址空间中了。
 还是Tig ht VNC中的Hook比较好一点,使用PostMessage函数,消息不会丢失。有一个疑
 问是,通过Hook DLL得到的区域一定是发生了屏幕变化的最小矩形区域吗?好像不是。
 
4.  全屏幕扫描
 前面讲到过,Hook无法得到屏幕的所有变化区域,还得辅之以屏幕扫描。Tight VNC中的
 屏幕区域扫描算法如下:(见PollArea函数)
 扫描方法是一次一行,由上至下,扫描线间距为32像素,依次取如下数组中的元素作为第
 一行扫描线的位置:
 const int vncDesktop::m_pollingOrder[32] = {
  0, 16,  8, 24,  4, 20, 12, 28,
 10, 26, 18,  2, 22,  6, 30, 14,
  1, 17,  9, 25,  7, 23, 15, 31,
 19,  3, 27, 11, 29, 13,  5, 21
   };
   对每行扫描线,以32像素长为单位,依次与前一次的屏幕比较,若有变化,则认为两条
   扫描线间32x32的矩形区域均有变化,记录到变化矩形列表中。若无变化,则认为两
   条扫描线间32x32的矩形区域均无变化。同样,通过Hook得到的屏幕区域变化也需要
   作记录。记录由Hook得到的屏幕变化区域见vncDesktopThread::run_undetached函数
   。
 
6. 程序流程
 
1) C++对象
 vncDesktop, vncDesktopThread
 vncClient, vncClientThread
 vncServer
 vncSockConnect, vncSockConnectThread
 vncEncodexxx
 vncRegion
 
 望文生意,很好理解各对象的作用。vncDesktop处理所有与桌面相关的消息,如用户操作
 ,Hook DLL的消息,等等。vncClient代表了一个vnc客户。vncServer意思明显,不必讲
 。vncSockConnect代表了网络连接,比如listen, accept就是它做的。vncEncodexxx的一
 系列类代表了各种编码方式。vncRegion代表了任意的屏幕区域,它提供了方法得到组成
 该区域的一系列矩形。
 
 基本上,都是先new xxx类,再调用它的Init函数,而该函数就会new一个xxxThread对象
 ,并调用xxxThread的Init函数,xxxThread的Init函数就会创建线程了,线程的入口函数
 是run()或run_undetached()。
 
2) 流程
 程序基本流程是,vncSockConnectThread对象accept一个客户后,就new一个vncClient对
 象 , 并 调用其Init函数,该函数new一个vncClientThread对象并调用它的Init函 
 数,该函数会启动与客户端进行RFB交互的线程,线程入口函数是:vncClientThread::ru
 n( )。
 唉,不想多讲了,其他的流程自己看吧,从main函数开始。
 

7. VNC的编码方法介绍(Hextile, RRE)

RRE编码算法的直观理解:(为提高效率,写程序的具体实现上与此不同)

设坐标原点在左上角,横坐标方向为向右,纵坐标方向为向下。

 

1)        依从左到右、从上到下的扫描顺序,找到第一个非背景色的像素点,设其坐标为(x, y)

2)        从该点往右搜索,直到发现与背景色不同的点,记其坐标为(x0, y)

3)        用一根长度为x0 - x的细线,放在(x, y) (x0, y)之间,然后用其从上往下扫,直到碰到某个像素点颜色与(x, y)的颜色不同。记此时线的右端点坐标为(hx, hy)

4)        从(x, y)往下搜索,直到发现与背景色不同的点,记其坐标为(x, y0)

5)        用一根长度为y0 - y的细线,放在(x, y) (x, y0)之间,然后用其从左往右扫,直到碰到某个像素点颜色与(x, y)的颜色不同。记此时线的下端点的坐标为(vx, vy)

6)        取两次扫描中扫过的较大的矩形,以RRE规定的格式保存。并用背景色标记该区域,表示已编码。

7)        重复以上步骤,至编码完毕。

 

Hextile编码算法:

Hextile算法是RRE算法的变体。需要编码的矩形区域被分为16x16的片(tile),这些片按从左往右、从上到下的顺序排列。当然,矩形区域的边缘会有不足16x16的片存在。对于每个片,用RAW或变体的RRE来编码。具体步骤如下:

 

1)        检查是否整个矩形是单色(solid),双色(mono),得到前景色(fg),背景色(bg)。bg为一个片中出现次数最多的颜色,fg为一个片中出现次数次多的颜色。

2)        若是单色,则BackgroundSpecified,编码完毕。

3)        若是双色,则BackgroundSpecified、ForegroundSpecified、AnySubrects,切记不可SubrectsColored,然后用RRE编码找出foreground对应的一系列子矩形,将此编码后数据量的大小与RAW编码方法的大小比较,取数据量较小的一种,编码完毕。这种情况下,若选择了RRE编码,则不需要填充RRE编码概念中的background color,因为它与fb相同,于是这一个byte大小的空间被节省了出来。

4)        若颜色数大于2种,则BackgroundSpecified、ForegroundSpecified、AnySubrects、SubrectsColored,然后用RRE编码,将此编码后数据量的大小与RAW编码方法的大小比较,取数据量较小的一种,编码完毕。

 

注意:

在以上4个步骤中,backgroundSpecified及foregroundSpecified还需要依据如下规则:

若一个片的前景色与它之前最近的设置了前景色的片前景色相同,则该片可以不设置前景色,背景色亦同此理。


8. 杂项
 Hook发出的消息是在vncDesktopThread::run_undetached中处理的
 是需要一块与全屏幕大小相同的内存区域来记录全屏幕图像的
 Mirror Driver还没有看,待续,或者,不再续了吧,呵呵

 

原文地址:http://my.chinaunix.net/space.php?uid=22915173&do=blog&id=172562

你可能感兴趣的:(TightVNC 源码学习)