Content List:
1.1 IActiveDesktop
Allows a client program to manage the desktop items and wallpaper on a local computer.
1.2 SetProcessShutdownParameters
The SetProcessShutdownParameters function sets shutdown parameters for the currently calling process. This function sets a shutdown order for a process relative to the other processes in the system.
1.3 One Instance Running by Mutex
Use Win32 Mutex object to insure that only one instance is currently running in our OS.
1.4 sscanf
Read formatted data from a string.
1.5 Kill Screen Saver
1.6 Disable Nagle Algorithm
Nagle Algorithm主要是用于优化小数据包的发送,用于在IP栈中缓冲小数据包,积累一定数量的小数据包一起发送。这样可以减少系统发包的数量, Nagle Algorithm实现时内部也有一个Timeout机制,但这个Timeout时间长度不能在外部设置。而且对每个IP栈的实现,这个Timeout Span 都有一个实现相关的值。
Screen Capture :
2.1 Poll Schema
Poll 方式就是最简单的一种方式了,也是大家在考虑截屏时最先想到一个解决方案。为取得系统的屏幕,poll方案每33ms轮询一次系统屏幕,将变化的部分添加加到一个UpdateRegion中。为获取这些屏幕变化的Rect,系统还采取了一种优化Polling算法,将屏幕分为32*32 pixel的小矩形块,算法给出了一个各个矩形块轮询的Order:
第一步:获取屏幕鼠标的HCURSOR
GetCursorPos获取屏幕鼠标位置,然后WindowFromPoint获取鼠标的拥有者窗口,GetWindowThreadProcessId获取相关线程ID,比较该ID 和GetCurrentThreadId返回的ID,如果相同,通过GetCursor直接获取鼠标的HCURSOR;否则,通过 AttachThreadInput连接上目标线程的Input Mechanism,再调用GetCursor获取鼠标的HCURSOR。
第二步:获取HCURSOR的Bitmap
现在有必要对HRGN操作相关API做一下介绍
2.2 TightVNC
TightVNC(Tight Virtual Network Computing)是一个远程桌面控制的开源软件,详情请参考 http://www.tightvnc.com .下载了TightVNC的代码,分析了一下其Server部分的代码, WinVNC下的文件很多,但我们按照它们各自的功能做一下划分,其结构如下:
其服务端的主要功能模块结构如下:
其核心框架就是四个类vncClient,vncServer,vncDesktop和vncBuffer.下面我就这四个类之间的联系和用途来作一下简单的分析:
vncServer:
vncServer 主要是做如下的一些工作:容许vncClient动态的添加和删除;将本地vncDesktop对象内部状态的任何改变"传播"到各个客户端;传播客户端的鼠标和键盘事件到本地的vncDesktop对象。同时,其还创建了vncSockConnect,vncCORBAConnect和 vncHTTPConnect来接受Socket,Corba和HTTP的连接。 vncServer为每个连接上来的客户端分配了一个ClientID(其实就是内部客户对象数组的Index),并且提供了对客户端管理的众多函数:
下面我们来看一下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 = new vncBuffer(m_desktop);并通过调用vncClient::SetBuffer为vncClient设置这个Buffer,最后将此用户添加到auth list中。
vncServer提供了一个用户列表的操作接口,这些接口通过将vncServer的方法调用映射到对auth list中各个客户的同样的方法的函数调用,这些方法有:
vncDesktop是一个全局唯一的对象,根据注释,vncDesktop主要是处理从display buffer中获取数据;同时,它还利用RFBLib DLL为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指针来获取相关的数据。
- System Shell
- Screen Capture
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 SetProcessShutdownParameters function sets shutdown parameters for the currently calling process. This function sets a shutdown order for a process relative to the other processes 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
Nagle Algorithm主要是用于优化小数据包的发送,用于在IP栈中缓冲小数据包,积累一定数量的小数据包一起发送。这样可以减少系统发包的数量, Nagle Algorithm实现时内部也有一个Timeout机制,但这个Timeout时间长度不能在外部设置。而且对每个IP栈的实现,这个Timeout Span 都有一个实现相关的值。
// 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*32 pixel的小矩形块,算法给出了一个各个矩形块轮询的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.c Log.cpp MinMax.cpp RectList.cpp stdhdrs.cpp tableinitcmtemplate.cpp tableinittctemplate.cpp tabletranstemplate.cpp translate.cpp vncauth.c vncInstHandler.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 = new vncBuffer(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中获取数据;同时,它还利用RFBLib DLL为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指针来获取相关的数据。