TightVNC分析文档
System Shell :
1.1 IActiveDesktop
Allows a client program to manage the desktop items and wallpaper on a local computer.
#include IActiveDesktop* active_desktop = 0; CoCreateInstance(CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER, IID_IActiveDesktop, (void**)&active_desktop);
1.2 SetProcessShutdownParameters
The SetProcessShutdownParametersfunction sets shutdown parameters for the currently calling process.This function sets a shutdown order for a process relative to the otherprocesses in the system.
// Set this process to be the last application to be shut down. SetProcessShutdownParameters(0x100, 0);
1.3 One Instance Running by Mutex
Use Win32 Mutex object to insure that only one instance is currently running in our OS.
const char mutexname [] = "WinVNC_Win32_Instance_Mutex"; BOOL vncInstHandler::Init() { // Create the named mutex HANDLE mutex = CreateMutex(NULL, FALSE, mutexname); if (mutex == NULL) return FALSE; // Check that the mutex didn't already exist if (GetLastError() == ERROR_ALREADY_EXISTS) return FALSE; return TRUE; }
1.4 sscanf
Read formatted data from a string.
// Check the protocol version int major, minor; sscanf((char *)&protocol_ver, "RFB %03d.%03d/n", &major, &minor);
1.5 Kill Screen Saver
// How to kill the screen saver depends on the OS switch (osversioninfo.dwPlatformId) case VER_PLATFORM_WIN32_WINDOWS: HWND hsswnd = FindWindow ("WindowsScreenSaverClass", NULL); if (hsswnd != NULL) PostMessage(hsswnd, WM_CLOSE, 0, 0); break; case VER_PLATFORM_WIN32_NT: HDESK hDesk = OpenDesktop( "Screen-saver", 0, FALSE, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS ); if (hDesk != NULL) { EnumDesktopWindows(hDesk, (WNDENUMPROC) &KillScreenSaverFunc, 0); CloseDesktop(hDesk); // Pause long enough for the screen-saver to close //Sleep(2000); // Reset the screen saver so it can run again SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, 0, SPIF_SENDWININICHANGE); } break; }
1.6 Disable Nagle Algorithm
NagleAlgorithm主要是用于优化小数据包的发送,用于在IP栈中缓冲小数据包,积累一定数量的小数据包一起发送。这样可以减少系统发包的数量,NagleAlgorithm实现时内部也有一个Timeout机制,但这个Timeout时间长度不能在外部设置。而且对每个IP栈的实现,这个TimeoutSpan 都有一个实现相关的值。
// Disable Nagle's algorithm BOOL nodelayval = TRUE; setsockopt(m_sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&nodelayval, sizeof(BOOL));
Screen Capture :
2.1 Poll Schema
Poll方式就是最简单的一种方式了,也是大家在考虑截屏时最先想到一个解决方案。为取得系统的屏幕,poll方案每33ms轮询一次系统屏幕,将变化的部分添加加到一个UpdateRegion中。为获取这些屏幕变化的Rect,系统还采取了一种优化Polling算法,将屏幕分为32*32pixel的小矩形块,算法给出了一个各个矩形块轮询的Order:
const int 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 };获取当前屏幕鼠标图像的思路:
第一步:获取屏幕鼠标的HCURSOR
GetCursorPos获取屏幕鼠标位置,然后WindowFromPoint获取鼠标的拥有者窗口,GetWindowThreadProcessId获取相关线程ID,比较该ID和GetCurrentThreadId返回的ID,如果相同,通过GetCursor直接获取鼠标的HCURSOR;否则,通过AttachThreadInput连接上目标线程的Input Mechanism,再调用GetCursor获取鼠标的HCURSOR。
第二步:获取HCURSOR的Bitmap
int :GetCursorSendBuffer(BYTE *pBuffer, int nSize) { .... ICONINFO IconInfo; GetIconInfo(hcursor, &IconInfo); BITMAP bmMask; GetObject(IconInfo.hbmMask, sizeof(BITMAP), (LPVOID)&bmMask); GetBitmapBits(IconInfo.hbmMask,bmMask.bmWidthBytes * bmMask.bmHeight, m_mBits); .... }
现在有必要对HRGN操作相关API做一下介绍
GetRgnBox 获取HRGN的边界矩形 GetRegionData 可以将一个HRGN分解为一个RECT数组,见RGNDATA结构体说明 CombineRgn 对HRGN操作RGN_AND,RGN_COPY,RGN_DIFF,RGN_OR或是RGN_XOR
2.2 TightVNC
TightVNC(Tight Virtual Network Computing)是一个远程桌面控制的开源软件,详情请参考 http://www.tightvnc.com.下载了TightVNC的代码,分析了一下其Server部分的代码, WinVNC下的文件很多,但我们按照它们各自的功能做一下划分,其结构如下:
Kernel | vncBuffer.cpp vncClient.cpp vncDesktop.cpp vncServer.cpp WinVNC.cpp |
GUI | vncAbout.cpp vncAcceptDialog.cpp vncAdvancedProperties.cpp vncConnDialog.cpp vncMenu.cpp vncProperties.cpp vncTimedMsgBox.cpp |
Misc | d3des.cLog.cpp MinMax.cpp RectList.cpp stdhdrs.cpp tableinitcmtemplate.cpptableinittctemplate.cpp tabletranstemplate.cpp translate.cpp vncauth.cvncInstHandler.cpp vncKeymap.cpp vncRegion.cpp< vncService.cpp |
Network | VSocket.cpp vncSockConnect.cpp vncHTTPConnect.cpp rfbproto.h |
Encoding | vncEncodeCoRRE.cpp vncEncodeHexT.cpp vncEncoder.cpp vncEncodeRRE.cpp vncEncodeTight.cpp vncEncodeZlib.cpp vncEncodeZlibHex.cpp |
其服务端的主要功能模块结构如下:
其核心框架就是四个类vncClient,vncServer,vncDesktop和vncBuffer.下面我就这四个类之间的联系和用途来作一下简单的分析:
vncServer:
vncServer主要是做如下的一些工作:容许vncClient动态的添加和删除;将本地vncDesktop对象内部状态的任何改变"传播"到各个客户端;传播客户端的鼠标和键盘事件到本地的vncDesktop对象。同时,其还创建了vncSockConnect,vncCORBAConnect和vncHTTPConnect来接受Socket,Corba和HTTP的连接。vncServer为每个连接上来的客户端分配了一个ClientID(其实就是内部客户对象数组的Index),并且提供了对客户端管理的众多函数:
virtual void DisableClients(BOOL state); virtual void KillClient(vncClientId client); virtual void KillAuthClients(); virtual void KillUnauthClients(); virtual vncClient* GetClient(vncClientId clientid); vncClientId AddClient(VSocket *socket, BOOL auth, BOOL shared); virtual void RemoveClient(vncClientId client);同时,vncServer还提供了对客户Teleport,Capability,KeyboardEnabled,PointerEnabled,Name,Authenticated属性的get/set方法。
下面我们来看一下vncServer对客户端连接上来和客户端认证成功这两个事件的处理流程:
vncServer::AddClient:
首先vncServer在其内部的vncClient*m_clientmap[MAX_CLIENTS]数组中为新连接上的客户端分配一个空闲的slot,并将其作为此客户的 clientID.然后,为此连接分配一个vncClient对象,根据传递过来的参数,设置vncClient对象的相关属性,然后调用vncClient::Init方法将vncServer的实例指针和 clientID传给vncClient实例。接着,m_clientmap[clientid] =client并将此用户加入vncServer的未认证用户链表。
vncServer::Authenticated(vncClientId clientid):
首先从未认证用户列表中根据clientid获取vncClient对象,并将其从unauth list中删除。如果是vncServer的第一个用户,创建vncDesktop对象,并调用m_desktop->Init(this)来初始化该vncDesktop对象。接下来,为这个用户分配一个vncBuffer *buffer = newvncBuffer(m_desktop);并通过调用vncClient::SetBuffer为vncClient设置这个Buffer,最后将此用户添加到auth list中。
vncServer提供了一个用户列表的操作接口,这些接口通过将vncServer的方法调用映射到对auth list中各个客户的同样的方法的函数调用,这些方法有:
virtual void TriggerUpdate(); virtual void UpdateRect(RECT &rect); virtual void UpdateRegion(vncRegion ®ion); virtual void CopyRect(RECT &dest, POINT &source); virtual void UpdateMouse(); virtual void UpdateClipText(LPSTR text); virtual void UpdatePalette();vncDesktop:
vncDesktop是一个全局唯一的对象,根据注释,vncDesktop主要是处理从display buffer中获取数据;同时,它还利用RFBLibDLL为vncServer提供诸如鼠标移动和屏幕更新等信息。上面提到,vncServer在第一个用户连接上来时发现其m_desktop为空时就创建一个vncDesktip对象,并调用vncDesktop::Init(this)对其初始化.在vcnDesktop::Init的实现中我们发现其创建了一个vncDesktopThread,vncDesktop的方法调用大部分都在这个vncDesktopThread里完成的.下面我们来分析一下这个线程都做了些什么:
vncDesktopThread::run_undetached(void *arg):
首先调用vncDesktop::Startup初始化,vncDesktop对象(见vncDesktop::Startup),然后就是处理桌面消息,调用 m_server->UpdateMouse()和m_server->UpdateRegion(rgncache),接下来调用vncServer::TriggerUpdate来发送屏幕更新到每个vncClient.然后就是处理 RFB_SCREEN_UPDATE和 RFB_MOUSE_UPDATE这两个注册消息。
vncClient:
vncClient做了数据发送的工作,在vncClient::SendUpdate函数的实现中,我们可以看到vncClient调用 SendRFBMsg首先发送 ,然后 SendCursorShapeUpdate发送鼠标形状更新, SendCursorPosUpdate发送鼠标Pos更新,发送 SendCopyRect,最后调用 SendRectangles发送需要更新的矩形的相关数据。其实每个客户端vncClient在调用vncClient::Init初始化的时候都开了一个线程,客户端的行为基本上都是在vncClientThread::run里完成的。该线程在跟客户端交互完成了认证,Pixel格式,Encoding算法等信息的协商后,就进入一个loop循环开始接受和处理远程客户端发过来的rfbSetPixelFormat,rfbSetEncodings,rfbFramebufferUpdateRequest,rfbKeyEvent,rfbPointerEvent,rfbClientCutText消息。
vncBuffer:
vncBuffer主要处理发送数据的Encoding工作,其提供了远程客户的本地视图,其主要是利用内部的vncDesktop指针来获取相关的数据。
Reference: