ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

VNC源码深度解析:RFB协议、图像编码与VC++编译实战

2026/9/9 8:56:50 拓冰建站 浏览量
VNC源码深度解析:RFB协议、图像编码与VC++编译实战 简介这是一套基于VC实现的VNC远程控制程序完整源码面向期望掌握RFB协议与远程桌面底层原理的C开发者也适合网络编程、图形渲染方向的学习者对照实践。压缩包共631个文件大小仅2.27MB核心代码由183个C文件、136个头文件和83个C文件构成同时附带VC工程文件vcproj/dsp/sln、文档及图标等辅助资源控制端与被控端代码分开组织便于逐个模块研读。目前已有851人学习下载。源码完整覆盖TCP/IP网络通信、屏幕图像捕获与编码、键盘鼠标事件转发、线程同步等VNC关键环节并集成jpeg图像压缩相关实现可帮助读者理解远程控制软件从底层协议到界面交互的完整链路工程中丰富的GDI调用和多线程设计也为后续扩展加密认证、性能优化或自定制功能提供了可直接修改的蓝本。1. VNC项目到底在解决什么问题1.1 一句话讲透VNC的工作方式VNC这名字看着高大上拆开就是Virtual Network Computing虚拟网络计算。它解决的事情特别直白让你在一台电脑的屏幕上看到并且操作另一台电脑的桌面。这个“另一台电脑”可以跟你在同一个办公室也可能在几千公里外的机房——只要网络能通就行。早年我第一次拿到VNC远程控制程序VC源码的时候第一反应是这东西怎么会有那么多文件。服务端、客户端、协议解析、编码器、认证模块、平台相关的绘图代码加起来几百个文件是常态。等你把代码捋顺了才发现核心逻辑其实就像一个“截屏转发”的管道服务端把屏幕抓下来压缩编码通过网络发给客户端客户端收到数据解码还原成图像显示出来再把你的键盘鼠标操作传回服务端。就这么一个循环构成了远程桌面的全部体验。1.2 为什么一份VC写的VNC源码值得研究如果你只是想在两台电脑之间远程控制说实话装个现成的VNC软件就够了甚至Windows自带的远程桌面也不错。但研究源码的人目的通常不是“用”而是“改”有人要定制一个轻量级远程工具嵌进自己的系统有人要解决特定分辨率、特定网络下的画面延迟问题有人想学习RFBRemote Framebuffer Protocol协议怎么落地还有人纯粹是想看老前辈用C怎么写网络程序和图像编码。这份VC源码的价值就在这里。它不只是一堆能编译的代码更是一套完整的远程控制协议实现范本。你不需要从零去啃RFC文档直接看代码就能明白握手、认证、帧缓冲更新、编码协商这些抽象概念是怎么变成实际字节流的。对于做嵌入式、做运维工具、做跨平台远控产品的人来说这相当于拿到了一套可以直接套用的骨架。1.3 版本扫盲TightVNC、UltraVNC怎么选网上常见的VNC源码主要分几个分支TightVNC、UltraVNC、RealVNC的开源版还有早期的标准VNC。我手上这份是VC工程对应TightVNC分支的可能性比较大因为TightVNC在Windows上的工程化做得最规整模块划分清晰适合学习。TightVNC的代码风格偏简洁默认编码采用Tight编码这也是它名字的由来在低带宽下表现不错源码阅读门槛相对低一些。UltraVNC的扩展功能多比如文件传输、聊天、漫游光标这些代码量更大二次开发功能找起来方便但对新手不太友好。如果你是为了学习我建议从TightVNC入手如果你是想在现有功能堆上改东西UltraVNC更合适。RealVNC开源版经过多次重构代码风格更现代但结构也相对复杂。2. 源码架构与三个核心协议机制2.1 RFB协议的三阶段握手、初始化、交互RFB协议是VNC的底层通信协议整个流程可以分成三个阶段。第一阶段是握手Handshake。客户端连上服务端的5900端口后双方先交换协议版本号比如3.3、3.7、3.8然后协商安全类型。老版本默认只用VNC Password认证新版本还支持None、TLS等。这个阶段的代码通常在ProtocolVersion和Security协商函数里数据结构就是几个结构体直接写到socket上非常简单粗暴。第二阶段是初始化Initialization。认证通过后服务端把屏幕的宽、高、像素格式比如RGB565还是RGB888、桌面名称发给客户端客户端收到后回一个标志位表示自己是否支持像素格式协商。从这一步开始双方才真正建立起“显示”的上下文。第三阶段是交互循环Interaction Loop。客户端会周期性地发FrameBufferUpdateRequest请求服务端收到请求后检查屏幕是否有变化把变化区域编码成字节流发回去客户端解码并显示。同时客户端把键盘事件、鼠标点击、鼠标移动这些消息打包发给服务端服务端模拟输入。源码里这一循环就是两个while循环一个是客户端的接收线程一个是服务端的发送线程理解了这两个线程就理解了VNC的一大半。2.2 图像编码Raw、Hextile、ZRLE怎么选画面传输如果直接按原始像素发数据量大到没法用。所以RFB协议定义了多种编码方式客户端在初始化时发送自己支持的编码列表服务端从中选择一种。这几个编码里Raw是最笨的办法像素点不压缩直接发适合内网极低延迟场景。Hextile把屏幕分成16x16的小块每块记录自己的位置、颜色数据如果块内颜色单一还可以只发一个颜色值压缩率比Raw好很多CPU开销也小。ZRLE用zlib压缩运行长度编码数据压缩率高但对CPU要求高适合带宽紧张的环境。TightVNC源码里还实现了Tight编码它是在ZRLE基础上的进一步优化会根据图像内容动态选择调色板编码、灰度编码或纯色填充还会做JPEG压缩预处理。研究编码器代码时我强烈建议你先跑一遍看效果再去看代码实现——这样你才能把“为什么这个编码器在桌面文字场景下表现好、在照片场景下表现差”这种问题跟代码对应起来。2.3 VNC认证是怎么保证安全的VNC的密码认证用的是挑战-响应机制过程不传明文密码。服务端在安全协商完成后生成一个16字节的随机挑战数据发给客户端客户端把这个随机数用密码的DES加密结果作为密钥做一次DES加密把加密结果返回给服务端服务端用自己保存的密码做同样的操作比对结果是否一致。不过要说句公道话VNC的这套认证放到今天是相当脆弱的。DES密钥有效长度只有56位VNC还把密码截断到8个字符这意味着暴力破解难度很低。源码里能看到一个固定密钥表desTable整个VNC生态都用这张表一旦密钥交换过程被截获密码就很容易被还原。所以如果你的VNC服务暴露在公网一定要配合防火墙白名单或者隧道使用不要把5900端口裸奔出去。这一点在阅读认证代码时值得反复体会协议设计有时代局限性二次开发时要主动加强安全性。3. VC工程编译实战清单3.1 编译前置环境与工程转换先说结论这份源码可以在Visual Studio 2017、2019甚至2022上编译但过程要花点心思。老版本的VNC源码多是VC6工程文件后缀是.dsp、.dsw新版VS打开时会有转换向导把它转成.vcxproj工程即可。转换之后有几个环境设置你大概率要手动调整。项目属性里要确保字符集用的是“使用多字节字符集”很多老代码风格依赖char而不是UnicodeC/C语言标准选“默认”就好不用强行改成C14、17老代码往往不兼容新标准。另外如果编译报错找不到Windows SDK去安装一下对应版本的SDK组件VS安装器里勾选就能装。3.2 从源码生成可执行文件的完整流程我以Windows平台为例整理一份可以直接照着做的流程下载源码压缩包解压到一个没有空格的纯英文路径比如 D:\vnc_src\避免老代码处理路径时出问题。用Visual Studio打开转换后的.sln文件右键解决方案选择“配置管理器”把平台切换为x64或者保持x86看你的目标环境。在解决方案里找到winvnc和vncviewer两个项目右键分别设为启动项目。winvnc是服务端vncviewer是客户端。编译分两步走先生成vnc_lib这类公共库项目如果有的话再生成winvnc和vncviewer。右键解决方案→重新生成解决方案VS会自动处理项目依赖。到输出目录找到生成的winvnc.exe和vncviewer.exe先在本机启动winvnc再用vncviewer连接127.0.0.1测试。整个过程顺利的话十分钟内能看到一个能跑的VNC系统。但老工程没这么听话下面这几个报错你大概率会碰到。3.3 编译期常见的五个坑报错现象原因处理方式无法打开包含文件 winsock2.hWindows SDK不完整或路径没配好确认安装了“Windows SDK”组件在项目属性VC目录里检查Include路径std::min/std::max 相关编译错误Windows.h里定义了min/max宏和STL冲突在预处理器定义里加 NOMINMAXstrcpy、sprintf 提示C4996新版VS的安全CRT警告预处理器里加 _CRT_SECURE_NO_WARNINGSerror C2440 类型转换错误老代码里有隐式类型转换新编译器不允许手动加static_cast修正优先看是char还是const char引起链接时找不到 wsock32.lib / ws2_32.lib没添加网络库依赖在项目属性的“链接器→输入→附加依赖项”里补充这两个lib我自己的经验是不要一报错就改代码。先检查项目属性这种“环境级”配置90%的老工程移植问题都是环境配置不对而不是代码逻辑有问题。真遇到需要改代码的地方最好用条件编译宏包起来别把老代码的逻辑破坏掉。4. 源码走读一条连接的一生4.1 服务端从监听端口到画面发送VNC服务端的入处在源码里通常是一个叫vncServer的类核心是一个监听socket默认端口5900。代码启动流程看着复杂其实就是一个标准的Windows socket服务创建socket、bind、listen然后accept循环等待客户端连接。每来一个客户端服务端就启动两个线程一个专门的线程做RFB握手和认证通过后进入消息循环另一个线程做屏幕轮询定时检查屏幕变化区域把变化内容编码发送。屏幕抓取在Windows老代码里多用BitBlt配合GetDC(NULL)也就是把整个桌面DC的内容拷到内存位图里再做差分比较。新版代码里有些实现改用了Desktop Duplication API效率更高但原理一样抓到屏幕变化标记脏矩形只编码这些区域。看服务端代码时我最建议你关注两个函数一个负责处理客户端发来的FrameBufferUpdateRequest另一个负责响应客户端设置像素格式。前者决定画面更新的触发时机后者决定图像字节的排列方式这两块写明白了VNC的画面传输机制你就通了一大半。4.2 客户端从密码输入到屏幕渲染客户端代码相对简单直接。启动后读取命令行参数连服务器弹出密码框走完握手认证流程。初始化完成后客户端定时通常是每秒5到20次发送FrameBufferUpdateRequest然后在一个循环里接收服务端数据。收到数据后的核心处理在vncDecoder里。它要先解析消息头判断消息类型是FrameBufferUpdate还是SetPixelFormat、SetEncodings等如果是图像更新消息就根据编码类型分派到对应的解码函数Raw解码就是memcpyHextile解码要处理16x16的块循环Tight解码要先解zlib再还原像素。解码完的图像数据往屏幕DC上贴用StretchDIBits或者AlphaBlend都行。客户端代码里有意思的地方是鼠标键盘事件的处理。鼠标事件被编码成相对服务端屏幕坐标的绝对坐标比如服务端屏幕是1920x1080你点击客户端窗口的像素点(960,540)发送的就是坐标(960,540)键盘事件则映射为X11键码格式和VNC协议里定义的keysym对应。这些映射关系在源码里有大段的switch case如果你想做自定义键位映射就在这里动手。4.3 二次开发时最值得改的三个位置基于这份源码做二次开发最常动刀的有三处。第一处是编码器选择逻辑在服务端的帧缓冲发送函数里。默认条件下它会根据网络延迟和上次发送大小自动选编码你可以改成固定用某一种编码比如局域网强制Hextile或者低带宽强制Tight。第二处是客户端认证流程如果你要做企业内部的无密码快捷登录可以跳过一次挑战响应直接发空密码或者预设凭证。第三处是消息循环里对未知消息类型的处理RFB协议预留了扩展消息号你可以自己定义消息类型做自定义指令比如远程执行命令、传输文件、发送心跳包。我见过有人把VNC改成远程考试监控工具就是往这条扩展通道里塞自定义数据。改代码之前一定先确认自己手上这份源码有没有线程安全问题因为VNC这种老项目都是裸线程加全局变量的风格。加一个标志位或者队列时记得用临界区或者原子变量保护不然改着改着就出现随机崩溃。5. 高频实战场景与对应修改思路5.1 分辨率自适应从固定桌面到动态调整很多人在使用VNC时遇到分辨率问题服务器端是4K屏客户端是1080P笔记本远程过去界面大得没法看。解决思路有两条路。一条是在服务端做分辨率缩放即采样屏幕后先缩放到客户端支持的分辨率再编码改动集中在编码发送前的图像处理环节需要引入一个缩放算法双线性插值就行。另一条是让客户端以窗口模式显示不做缩放靠滚动条看整个桌面。源码里通常两种都支持窗口模式下客户端会把服务端桌面大小当作窗口客户区大小然后处理WM_HSCROLL和WM_VSCROLL消息。如果你有root权限的Linux服务器也可以用xrandr这类工具直接改服务端分辨率配合源码里的分辨率变更通知消息客户端会自动刷新桌面大小。5.2 文件传输协议本身没有但可以自己加这是VNC最被吐槽的点之一标准RFB协议没有文件传输功能。现成VNC软件里的文件传输都是各自扩展实现的。如果用UltraVNC源码它本身就带了文件传输通道数据走的是自定义消息号你在客户端和服务端各写一个文件列表请求、传输请求的处理函数就能跑起来。如果用TightVNC需要自己扩协议在双方协商好扩展消息范围比如消息号247以上后定义文件元数据、文件内容、传输结束三类消息然后实现分块读写。文件内容消息的结构可以很简单消息号文件名长度文件名文件数据块每块不超过32KB避免UDP分包问题。这个工程量不大但如果中途要断点续传就得加偏移量字段和校验值复杂度会上一个台阶。5.3 开机自启和后台运行Windows桌面版的VNC服务端默认是普通进程开机自启通常靠注册表Run键。这有个坑用户没登录时服务端不会启动或者启动后没有交互桌面。热词里提到的“ubuntu 22.04安装vnc 关闭终端就失效”就是这个问题的Linux版本本质都是服务端进程依附于某一个用户会话会话关闭进程就跟着退出。解决思路是把vnc server改造成Windows服务。VC源码里一般会有Service相关的代码文件比如winservice.cpp把服务端注册成Service后用Service Control Manager管理设置自动启动。注意VNC这种需要访问用户桌面的程序注册成服务后还要考虑会话0隔离的问题Windows下会给会话0单独一块非交互空间你的服务如果没有特殊处理就看不到真实桌面。常规做法是让服务保持在LocalSystem账户下运行并且勾选服务属性里的“允许服务与桌面交互”或者使用创建一个用户进程并返回Session 1的逻辑。Linux下的思路类似把vncserver写进systemd的service单元指定User和Display变量开机自启就不会随终端关闭。6. 踩坑实录与常见问题排查6.1 日常使用中的高频问题结合VNC的使用习惯和源码实现我把高频问题整理成了一份速查表可以根据现象快速定位。现象可能原因排查/解决思路客户端能连接但黑屏服务端被抓屏的GDI接口返回空DC或者会话锁屏检查服务端是否运行在锁定会话改用服务端本机登录状态看代码里GetDC(NULL)是否失败画面一直刷新却很卡编码器选择了Tight/JPEGCPU占用过高改为Hextile或Raw测试降低刷新率缩小分辨率连上后几秒就断开认证成功但初始化消息响应超时防火墙拦截了后续数据包抓包看断开前最后一个消息关闭防火墙测试确认VNC端口没有被端口占用冲突鼠标点击位置不对服务端和客户端分辨率不一致坐标映射逻辑出错检查客户端的屏幕坐标系换算代码调整缩放比例为1:1测试远程复制粘贴失效剪贴板处理线程没有及时同步有的分支实现了剪贴板消息同步检查WM_CLIPBOARDUPDATE消息的注册是否成功修改密码后连接总是失败服务端密码文件没刷新或配置文件路径指错确认密码文件写入路径删除旧密码文件重启服务端6.2 调试远程控制程序的独门技巧调试VNC这类网络图形程序常规断点手段不够用因为它同时涉及网络、图形和多线程。我有三个习惯分享出来。第一个习惯是开日志宏。老VNC源码里基本都有调试日志的开关通常在common.h或者vnc.h里定义一个DEBUG宏打开后运行目录会输出日志文件记录每个消息的收发时间、消息类型、长度。碰到疑难杂症先看日志比瞎猜快得多。第二个习惯是抓包。wireshark抓本地回环的5900端口流量能看到协议交互的每个字节。配合RFC6143文档你会清晰地看到握手时版本号、安全类型、随机挑战数据的流转也能直观地看到FrameBufferUpdate消息里编码数据的分布。把这个习惯养成调试任何自定义协议都有底气。第三个习惯是虚拟桌面测试。不要在真实工作机上反复试分辨率修改和全屏代码容易把环境搞崩。用Windows自带的虚拟桌面功能或者单独建一个低分辨率虚拟机来跑测试确认代码稳定后再拿到真实环境验证。6.3 我踩过的坑和补救方式以前我改过一套VNC源码做远程协助工具在编码器选择上想当然地全程用Tight编码结果内网测试时CPU占用率飙到90%画面延迟严重。后来分析发现Tight编码的压缩计算量太大在局域网带宽充足的情况下反而是个累赘。改成“局域网自动用Hextile弱网自动切Tight”的策略后问题立刻解决。这件事给我的教训是VNC源码里的默认参数往往经过项目团队的实践调整改之前要理解他们的设计意图不要一味追求新特性。另一个坑是字符编码混乱。老代码大量使用char数组和strcpy在VS2019上编译通过后运行起来中文桌面名显示为乱码因为字符串被当成了ANSI而系统是UTF-8环境。统一改用MultiByteToWideChar转换一次或者把项目字符集调整成“使用Unicode字符集”并适配相关API调用才彻底解决。最后还有一件事提醒所有做VNC二次开发的同行VNC这个协议太老了很多基础设计密码认证、图像编码、消息协商都有明显的时代印记。你可以在源码基础上升级它但务必保留协议兼容层不然客户端和服务端的版本一旦不匹配就会出现更新一个端就断连的尴尬。我在实际改这套代码时最大的体会是VNC的源码没有多高深难的是它横跨的网络、图形、输入模拟、并发处理这几个领域都要求你有基础功底。但反过来想正是因为它的综合性啃完一份VNC源码你对Windows网络编程、GDI绘图、多线程协作的理解都会上一个台阶。如果你也是奔着这个目标来的那就别急着跑通一个demo就收工多花几个晚上把代码读透收获一定比你预想的大。本文还有配套的精品资源点击获取