使用TightVNC一段时间后,一直好奇它是如何实现用浏览器访问服务器端,今天就分析一下它的技术原理。用任一浏览器访问一台服务器例如http://192.168.1.102:5800,等其加载完后查看网页源文件可以看到下面的HTML代码:
<HTML>
<HEAD><TITLE>TightVNC desktop [jujumao]</TITLE></HEAD>
<BODY>
<APPLET CODE=VncViewer.class ARCHIVE=VncViewer.jar WIDTH=1280 HEIGHT=1056>
<PARAM NAME="PORT" VALUE="5900">
</APPLET><BR>
<A HREF="http://www.tightvnc.com/">www.TightVNC.com</A>
</BODY>
</HTML>
代码很简单,稍微阅读一下就可知道它其实是通过JAVA的APPLET小程序来实现用浏览器服务器的,而且可以知道运行的是VncViewer.jar,入口在VncViewer.class,启动时向服务器传递一个参数PORT,值是5900。5900是默认服务器监听的端口,VncViewer客户端 就是通过这个端口连接服务器的。这样我们就可以大概知道:客户端用浏览器访问服务器5800端口,服务器接收到请求后返回上面的HTML代码,这样浏览器就开始执行这段HTML代码,代码中有APPLET标签,最后运行的就是从服务器下载的VncViewer.jar这个文件来实现跟服务器之间的控制。本人利用一些HTTP协议的数据包截取工具发现,从访问到可以用浏览器进行控制的全过程,只有两次的HTTP请求,在进行控制的时候并没有发生HTTP协议数据包,即可从其表现上判定,实质是运行VncViewer.jar来实现对服务器的控制的。
然而实际的源码是否也如猜测一样?打开TightVNC源码工程,找到vncServer类,里面有一个使用启用HTTP服务器的函数:
vncServer::SetHttpdEnabled(BOOL enable_httpd, BOOL enable_params)
{
if (enable_httpd != m_httpd_enabled) {
m_httpd_enabled = enable_httpd;
m_httpd_params_enabled = enable_params;
BOOL socketConn = SockConnected();
SockConnect(FALSE);
SockConnect(socketConn);
} else {
if (enable_params != m_httpd_params_enabled) {
m_httpd_params_enabled = enable_params;
if (SockConnected()) {
SockConnect(FALSE);
SockConnect(TRUE);
}
}
}
return TRUE;
}
更看SockConnect函数中启动HTTP服务器的代码:
if (m_httpConn == NULL && m_httpd_enabled && m_port_http != m_port) {
m_httpConn = new vncHTTPConnect;
if (m_httpConn != NULL) {
// Start up the HTTP server
if (!m_httpConn->Init(this, m_port_http,
m_httpd_params_enabled)) {
delete m_httpConn;
m_httpConn = NULL;
return FALSE;
}
}
}
这里的m_httpConn是一个vncHTTPConnect类,它的Init函数其实是启动了一个连接的vncSockConnectThread线程,而实际最后运行HTTP协议的代码在它run函数中的DOHTTP函数中// Code to be executed by the thread
void vncHTTPConnectThread::run(void *arg)
{
vnclog.Print(LL_INTINFO, VNCLOG("started HTTP client thread\n"));
// Perform the transaction
DoHTTP(m_socket);
// And close the client
delete m_socket;
vnclog.Print(LL_INTINFO, VNCLOG("quitting HTTP client thread\n"));
}
DOHTTP函数对浏览的HTTP请求作解析
// Switch, dependent upon the filename:
if (filename[1] == '\0' || (m_allow_params && filename[1] == '?'))
如果是没有参数或有参数就进行解析,并发送HTTP代码,如下:
// Send the java applet page
sprintf(indexpage, HTTP_FMT_INDEX,
desktopname, width, height+32, m_server->GetPort(), params);
HTTP_FMT_INDEX定义如下:
const char HTTP_FMT_INDEX[] =
"<HTML>\n"
" <HEAD><TITLE>TightVNC desktop [%.256s]</TITLE></HEAD>\n"
" <BODY>\n"
" <APPLET CODE=VncViewer.class ARCHIVE=VncViewer.jar WIDTH=%d HEIGHT=%d>\n"
" <PARAM NAME=\"PORT\" VALUE=\"%d\">\n"
"%.1024s"
" </APPLET><BR>\n"
" <A HREF=\"http://www.tightvnc.com/\">www.TightVNC.com</A>\n"
" </BODY>\n"
"</HTML>\n";
其他情况就传输资源文件中编译的文件。
// Now search the mappings for the desired file
for (int x=0; x < filemappingsize; x++)
打开工程文件的资源文件可以发现VncViewer.jar和.class文件都被编译在了WinVncServer.exe里面去了。