RealVNC源码学习笔记 三

本文详细介绍了RealVNC服务器端的源码流程,从VNCServerWin32构造函数开始,解析了如何通过事件驱动的方式管理服务器。在事件队列中,包括了SDisplay事件、RegConf事件、VNCServerWin32事件以及两个TCP监听事件。在`run`函数中,服务器进入消息循环,等待事件触发,当有客户端连接时,会调用SocketManager处理新的TCP套接字并实现RFB协议。通过对SocketManager::processEvent函数和VNCSConnectionST类的分析,展示了如何处理RFB协议的通信过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RealVNC源码学习笔记 三

1、 RealVNC服务器端代码流程解析

   RealVNC服务器端的main函数在winvnc模块的winvnc.cxx文件中,main函数很简单,该函数前面都是一些log信息设置,重点是构造VNCServerWin32对象后运行,该对象的run函数。VNCServerWin32构造函数如下:

VNCServerWin32::VNCServerWin32()

  : command(NoCommand),commandSig(commandLock),

    commandEvent(CreateEvent(0,TRUE, FALSE, 0)),

   vncServer(CStr(ComputerName().buf), &desktop),

    hostThread(0), runServer(false),isDesktopStarted(false),

    httpServer(&vncServer),config(&sockMgr), trayIcon(0),

    rfbSock(&sockMgr),httpSock(&sockMgr),

    queryConnectDialog(0)

{

  // Initialise the desktop

 desktop.setStatusLocation(&isDesktopStarted);

  // Initialise the VNC server

  vncServer.setQueryConnectionHandler(this);

  // Register the desktop's event tobe handled

  //添加SDisplay事件到事件队列中(SDisplay继承自SDesktop

 sockMgr.addEvent(desktop.getUpdateEvent(), &desktop);

  // Register the queued commandevent to be handled

  //添加VNCServerWin32的事件到事件队列中

  sockMgr.addEvent(commandEvent,this);

}

VNCServerWin32的参数初始化列表中会构造RegConf类,RegConf构造函数中会添加一一个Regconf事件到事件队列中。这样经过VNCServerWin32构造函数就添加了三个事件到事件队列中。

VNCServerWin32run函数代码如下:

int VNCServerWin32::run() {

  //

  { Lock l(runLock);

    hostThread = Thread::self();

    runServer = true;

  }

  // - Create the tray icon (ifpossible)

  //创建处理任务栏通知区VNC图标

  trayIcon = newSTrayIconThread(*this, IDI_ICON, IDI_CONNECTED, IDR_TRAY);

  // - Register for notification ofconfiguration changes

  config.setCallback(this);

  if (isServiceProcess())

   config.setKey(HKEY_LOCAL_MACHINE, RegConfigPath);

  else

    config.setKey(HKEY_CURRENT_USER,RegConfigPath);

  // - Set the address-changedhandler for the RFB socket

  //通过层层调用,会创建两个监听tcp,分别监听5900tcp port)和5800http-port)端口

  //并将套接字关联的事件加入到事件队列中

 rfbSock.setAddressChangeNotifier(this);

  DWORD result = 0;

  try {

    vlog.debug("Enteringmessage loop");

    // - Run the server until we'retold to quit

    MSG msg;

    int result = 0;

    //主循环

while (runServer) {

 //获得消息,在getMessage中会调用消息(事件)的拥有这对消息(事件)进行处理

      result =sockMgr.getMessage(&msg, NULL, 0, 0);

      if (result < 0)

        throwrdr::SystemException("getMessage", GetLastError());

      if (!isServiceProcess()&& (result == 0))

        break;

      TranslateMessage(&msg);

      DispatchMessage(&msg);

    }

    vlog.debug("Server exitedcleanly");

  } catch (rdr::SystemException&s) {

    vlog.error(s.str());

    result = s.err;

  } catch (rdr::Exception &e) {

    vlog.error(e.str());

  }

  { Lock l(runLock);

    runServer = false;

    hostThread = 0;

  }

  return result;

}

run函数中,由添加两个tcp事件到事件队列中。这样就在事件队里中添加了五个事件。也就是说在没有客户端连接之前,RealVNC服务器的时间队列中有五个事件。这五个事件的详情如下表:

序号

添加位置

事件拥有者类

事件用途

1

VNCServerWin32构造函数

Regconf

配置有更新时触发

2

VNCServerWin32构造函数

SDisplay

wm_hook.dll截获有用消息时触发

3

VNCServerWin32构造函数

VNCServerWin32

VNCServerWin32命令行触发

4

VNCServerWin32::run函数

SocketManager (与监听套接字关联 监听端口5900

由客户端请求连接时触发

5

VNCServerWin32::run函数

SocketManager (与监听套接字关联 监听端口5800

http型客户端请求连接时触发

事件2 SDisplay事件是怎么反应wm_hook.dll消息的,请参看“源码学习笔记一”。再来看一下run函数中调用的GetMessages函数:

BOOL EventManager::getMessage(MSG* msg, HWND hwnd, UINT minMsg, UINTmaxMsg) {

 while (true)

  {

     // - Process any pending timeouts

     DWORD timeout = checkTimeouts();

     if (timeout == 0)

          timeout = INFINITE;

     

     // - Events take precedence over messages

     DWORD result;

     if (eventCount)

     {

          // - Check whether any events are set

          //等待事件列表中有事件被触发

          result = WaitForMultipleObjects(eventCount,events, FALSE, 0);

          if (result == WAIT_TIMEOUT) //等待超时

          {

              // - No events are set, so check for messages

              if (PeekMessage(msg, hwnd, minMsg, maxMsg,PM_REMOVE))

                  return msg->message != WM_QUIT;

              // - Block waiting for an event to be set, ora message

              result = MsgWaitForMultipleObjects(eventCount,events, FALSE, timeout,

                  QS_ALLINPUT);

              if (result == WAIT_OBJECT_0 + eventCount)

              {

                 // - Return the message, if any

                  if (PeekMessage(msg, hwnd, minMsg, maxMsg,PM_REMOVE))

                      return msg->message != WM_QUIT;

                  continue;

              }

          }

     }

     else

          return GetMessage(msg, hwnd, minMsg, maxMsg);

     //事件队列中的某个事件被触发

     if ((result >= WAIT_OBJECT_0) && (result < (WAIT_OBJECT_0+ eventCount)))

     {

          // - An event was set - call the handler

           //计算被触发的事件在事件队列中的位置

          int index = result - WAIT_OBJECT_0;

          //调用被触发事件的拥有者的processEvent函数,处理事件。

         handlers[index]->processEvent(events[index]);

     }

     else if (result == WAIT_FAILED)

     {

          // - An error has occurred, so return theerror status code

          return -1;

     }

  }

}

getMessage函数可以看出,当事件被触发后,事件的拥有者来对该事件进行处理。这样就通过事件驱动了整个vnc系统。当由客户端请求连接时,就会调用SocketManagerprocessEvent函数,来创建一个新的tcpsocket与客户端进行连接,并将此tcpsocket添加到SocketManager中,将与此tcpsocket关联的事件添加到事件队列中,这样一来,VNC系统就可以处理新建tcpsocket的事件了。

当客户端与服务器端建立连接之后,服务器端和客户端就需要基于RBF协议就行会话通信。接下来分析一下RealVNC实现RFB协议的具体过程。

2、 RealVNC服务器端RFB协议的实现

通过以上的分析可知当由套接字事件被触发时将会调用SocketManagerprocessEvent

函数:

void SocketManager::processEvent(HANDLE event) {

  //判断事件是监听套接字的事件,还是已连接套接字事件

  if (listeners.count(event)) {//监听套接字的事件

     ……//省略部分代码

  } else if(connections.count(event)) {//已连接套接字事件

    //获得已连接套接字信息

    ConnInfo ci =connections[event];

    try {

      // Process data from an activeconnection

 

      // Cancel event notificationfor this socket

      if (WSAEventSelect(ci.sock->getFd(),event, 0) == SOCKET_ERROR)

        throwrdr::SystemException("unable to disable WSAEventSelect:%u",WSAGetLastError());

      // Reset the event object

      WSAResetEvent(event);

      // Call the socket server toprocess the event

      //调用与该套接字关联的服务器对象的processSocketEvent,处理该套接字的收发,完成、//RFB协议 serverVNCServerST类的对象

      ci.server->processSocketEvent(ci.sock);

      if (ci.sock->isShutdown()){

        remSocket(ci.sock);

        return;

      }

    ……//省略部分代码

  }

}

SocketManager::processEvent的源代码可知,processEvent函数通过调用会与该套接字的服务器类VNCServerSTprocessSocketEvent函数来处理已连接的套接字收发(即RFB协议)。接下来看一下VNCServerSTprocessSocketEven的源码:

void VNCServerST::processSocketEvent(network::Socket* sock)

{

  // - Find the appropriateVNCSConnectionST and process the event

 std::list<VNCSConnectionST*>::iterator ci;

  for (ci = clients.begin(); ci !=clients.end(); ci++) {

if ((*ci)->getSock() == sock){

  //调用VNCSConnectionSTprocessMessages()函数处理套接字事件

      (*ci)->processMessages();

      return;

    }

  }

  throw rdr::Exception("invalidSocket in VNCServerST");

}

VNCSConnectionST类继承自SConnection,是为VNCServerST设计的一个客户端连接类。每一个已连接的客户端(套接字)都拥有一个VNCSConnectionST类对象。可以认为VNCSConnectionST类就是用来维护、处理与客户端连接的套接字的。VNCSConnectionSTprocessMessage源码如下:

void VNCSConnectionST::processMessages()

{

  if (state() == RFBSTATE_CLOSING)return;

  try {

    // - Now set appropriate sockettimeouts and process data

    setSocketTimeouts();

    //requested.is_empty()不为空时clientsReadyBefore为真

    bool clientsReadyBefore =server->clientsReadyForUpdate();

    //查询输入流中是否有1个字节可读以非阻塞方式),有返回真没有返回false

    while(getInStream()->checkNoWait(1)) {

        //根据RBF的当前状态进行响应处理

      processMsg();

    }

 

    if (!clientsReadyBefore&& !requested.is_empty())

        //通知当前客户端已经准备好跟新数据

     server->desktop->framebufferUpdateRequest();

  } catch (rdr::EndOfStream&) {

    close("Cleandisconnection");

  } catch (rdr::Exception &e) {

    close(e.str());

  }

}

VNCSConnectionSTprocessMessage函数中从要处理的已连接套接字读数据,当可以读到数据时,说明客户端向服务器端发送了信息,这时就调用processMsg函数,来处理客户端发来的信息。processMsg函数在VNCSConnectionST的父类SConnection中实现:

//根据RFB当前的状态,调用不同的函数

void SConnection::initialiseProtocol()

{

  //发送RFB版本号给对方

 cp.writeVersion(os);

  state_ =RFBSTATE_PROTOCOL_VERSION;

}

 

void SConnection::processMsg()

{

  switch (state_) {

  case RFBSTATE_PROTOCOL_VERSION:processVersionMsg();      break;

  case RFBSTATE_SECURITY_TYPE:    processSecurityTypeMsg(); break;

  case RFBSTATE_SECURITY:         processSecurityMsg();     break;

  case RFBSTATE_INITIALISATION:   processInitMsg();         break;

  case RFBSTATE_NORMAL:           reader_->readMsg();       break;

  case RFBSTATE_QUERYING:

    throwException("SConnection::processMsg: bogus data from client while "

                   "querying");

  case RFBSTATE_UNINITIALISED:

    throwException("SConnection::processMsg: not initialised yet?");

  default:

    throwException("SConnection::processMsg: invalid state");

  }

}

RFB协议规定,握手始于服务器向客户发送协议版本的消息,以告知客户服务器所能支持RFB协议的最高版本号。因此,在客户端与服务器建立连接后,服务器就应立即向客户端发送RFB版本号,RealVNC可正式这么做的,在建立与客户端的连接后就立即调用VNCSConnectionSTinit函数从而调用SConnection::initialiseProtocol()函数先客户端向发送RFB版本号,然后将RFB状态设为RFBSTATE_PROTOCOL_VERSION。在SConnection::processMsg()函数中根据RFB当前的状态,调用相应的函数与客户端通信,并在调用的函数中修改RFB状态。这样就实现了RFB协议。

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值