2024年1月3日发(作者:)

下面的内容是用 的调试器调试的整个源码而确定的执行流程. 在分析代码时尽量不要静态的分析代码,这样速度很慢的。利用调试器我们可以通过简单的设置断点来跟踪整个执行流程。

WINVNC 调试其整体流程

在 WinMain 中进行

(1) 初始化套接字库 VSocketSystem

(2) 解析命令行参数

(3) 调用主窗口过程 WinVNCAppMain

WinVNCAppMain 中进行

(1)

确认当前只有一个本实例运行,vncInstHandler

(2)

创建 Server 类, 该类的作用:

动态添加和删除客户端

把本地窗口的更新发送给所有连接的客户端

把客户端的鼠标和键盘事件传递给本地

创建套套接字的连接

(3)

创建菜单和托盘图标 vncMenu

vncMenu中进行:

(1)

构造函数中进行:创建托盘图标窗口

把窗口托盘图标句柄加入到 vncServer.m_notifyList 中

初始化 vncProperties->Init

在调用 Init 中 vncProperties->Load 调用 vncProperties ->ApplyUserPrefs 调用

server->SockConnect 完成各种线程的创建和端口邦定工作。

在 Init 中得到密码检查是否需要验证。

当我们双击图标时vncProperties->ApplyUserPrefs会被调用,

vncProperties->ApplyUserPrefs 调用 vncServer::SockConnect 完成可能的程序重新启动。

当有客户端连接时:

当有客户端连接时,run_undetached 线程接收到连接后调用 AddClient 把客户端添加到一个客户端的映射数组中 Key 是 ClientSocket,值是新建的客户端的类。

然后创建客户端线程.

把客户端添加到未授权客户端列表.

客户端线程的工作(处理与客户端相关的工作):

客户端线程类vncClientThread的Run函数,就相当于线程函数(在线程中被调用)。

在vncClientThread->run 函数中调用 vncClientThread::InitVersion() 函数,

InitVersion 函数中工作如下:

(1)

首先向客户端发送自己的(服务器方的)版本号

(2)

接收客户端的版本号

(3)

验证版本号

客户端线程类vncClientThread的Run函数调用vncClientThread->InitAuthenticate

InitAuthenticate的工作是:

(1)

给客户端发送认证请求

(2)

验证客户端是否合法

vncDesktopThread->run_undetached 线程的工作(最核心的功能):

(1)

该线程调用vncDesktop::Startup():

进行所有的初始化工作:

1.

设置象素格式和位图信息

2.

设置各种系统 Hook, 添加系统挂钩,包括屏幕, 键盘,鼠标。

3.

设置一个定时器来处理拉模式(polling mode),每一秒钟执行一次.

这样 TriggerUpdate 例程每秒钟被执行一次.

(2)

设置处理剪切板消息

(3)

创建一个缓存区域对象。所有的区域更新消息都被缓存在该对象中,仅当 TriggerUpdate被触发时,才把这些消息传给所有的客户端。

认证流程:

1.

版本认证过程:

去掉该版本认证过程。

该版本认证过程:大致是服务器端

WinVNC 客户端分析

WinMainàVNCviewerApp32::NewConnection(创建ClientConnection 对象)à

ClientConnection::Run

ClientConnection::Run 的功能:

(1)

弹出窗口,接收用户输入的服务器的IP地址和端口号

(2)

取得连接信息,进行连接 ClientConnection::GetConnectDetails()

(3)

版本信息的认证 ClientConnection::NegotiateProtocolVersion()

(4)

进行权限的验证 ClientConnection::Authenticate();

修改时间:2007年8月25日星期六

修改内容:去掉了 ,AuthDialog.h ,这个认证会话框的内容是客户端输入服务器的密码才能对服务器端进行远程控制。

修改时间:8/25/2007 9:43:10 AM

修改内容:去掉 和log.h 内容

这个是对各个操作结果内容的记录

对于客户端的代码

该功能实现的一个重要方法:

ClientConnection 类的创建主显示窗口,窗口过程是静态方法

Static ClientConnection::WinProc

ClientConnection 类的创建一个线程,

在线程中调用了 ClientConnection ::run_undetached 这就相当于该类中的方法是线程方法一样。

ClientConnection::run_undetached 分析:

run_undetached 是假的线程方法()中

实现方法可能是:该方法是虚函数,在基类中就已经把该方法作为线程的参数传给了start 函数,该参数是作为函数指针传递的,这样在Start函数中利用函数指针调用该函数就可以了。

继承类只需把run_undetached当做线程方法实现就行了。(这样做的原因是:在线程中不允许线程方法作为其成员,编译不过,是调用约定的问题)

该例程的实现过程:

接收服务器端发送过来的消息类型: frame 更新,ReadBell 等。然后 switch 进入对应的分。

对于 FrameUpdate 的后续处理是:

1.

读取服务器端更新的Rect数目 RectCount,然后进入for循环,次数RectCount

2.

读取每个Rect的头部信息,确定这个Rect的坐标和宽度和高度,以及编码信息

3.

根据编码信息进入swtich 的特定分支, 接收真正的数据

4.

把接收到的 Rect 区域坐标转换成 Windows 坐标。

TightVNC分析文档

Content List:

System Shell

o

IActiveDesktop

o

SetProcessShutdownParameters

o

One Instance Running by Mutex

o

sscanf

o

Kill ScreenSaver

o

Diable Nagle Algorithm

Screen Capture

o

Poll Schema

o

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 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.%03dn", &major, &minor);

1.5 Kill Screen Saver

// How to kill the screen saver depends on the OS

switch (formId)

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(k, sizeof(BITMAP), (LPVOID)&bmMask);

GetBitmapBits(k,hBytes * ht,

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)是一个远程桌面控制的开源软件,详情请参考.下载了TightVNC的代码,分析了一下其Server部分的代码, WinVNC下的文件很多,但我们按照它们各自的功能做一下划分,其结构如下:

Kernel

GUI

d3des.c

vncauth.c

<

Misc

Network rfbproto.h

Encoding

其服务端的主要功能模块结构如下:

其核心框架就是四个类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指针来获取相关的数据。