简介:这份源码包面向希望深入理解远程桌面与远程控制实现原理的开发者,尤其适合具备一定网络编程与C++基础、想通过真实项目源码提升技能的中高级学习者。包内共40个文件,以14个cpp源文件与14个h头文件为核心,辅以6个dll动态库、2个exe可执行程序及2个pro工程文件,整体约6.13MB,覆盖客户端与被控端的连接建立、用户认证、屏幕捕获编码、输入同步及图像解码渲染等关键模块。已有156人学习下载。通过研读这套源码,读者可掌握socket通信与协议解析、密码加密与哈希验证、屏幕更新算法与跨平台GUI库的工程组织方式,并理解多线程编程、带宽自适应与延迟容忍等性能优化思路,为自行构建高效安全的远程控制方案提供可复用的参考实现与排错线索。
1. 拆开这个 remote_control 源码包:一套能跑起来的 Qt4 远程桌面原型
很多人第一次接触远程桌面,是从 Windows 自带的 mstsc 开始的,点一下连接就能操作另一台机器,用久了反而忘了它背后是一整套网络通信加图像编码的工程。这个remote_control_remote_远程桌面_远程控制软件_源码.rar不太一样,它把客户端和服务端的源码都摊开给你看,目录里躺着client、cserver、src三个文件夹,还有client.exe、cserver.exe两个编译好的可执行文件,以及QtCore4.dll、QtGui4.dll、QtNetwork4.dll、mingwm10.dll、libgcc_s_dw2-1.dll、libstdc++-6.dll这一串运行库。说白了,这是一套基于 Qt4 和 MinGW 编译的远程控制原型,能连、能看、能控,代码量不大,适合拿来拆解远程桌面的最小闭环。如果你正在找一份能直接读、能改、能复现的远程控制源码,而不是只停留在协议文档层面,这个包值得花一个下午跑一遍。
2. 从目录结构看这套源码的技术选型:为什么是 Qt4 + MinGW
2.1 目录里每个文件在干什么
先把压缩包解开,别急着双击 exe。我一般会先列一遍目录,确认哪些是源码、哪些是编译产物、哪些是运行依赖。这个包的布局很典型:
| 路径/文件 | 类型 | 作用 |
|---|---|---|
src/ | 源码目录 | 客户端与服务端的核心实现,连接、屏幕捕获、输入同步都在这 |
client/ | 客户端工程 | 发起连接、显示远程画面、回传键鼠事件 |
cserver/ | 服务端工程 | 监听端口、捕获屏幕、接收并执行控制指令 |
client.exe | 可执行文件 | 已编译的客户端,双击即可发起连接 |
cserver.exe | 可执行文件 | 已编译的服务端,双击后进入监听状态 |
QtCore4.dll | 运行库 | Qt4 核心库,信号槽、容器、线程都靠它 |
QtGui4.dll | 运行库 | Qt4 图形库,窗口、绘图、事件循环 |
QtNetwork4.dll | 运行库 | Qt4 网络库,QTcpSocket、QTcpServer 封装 |
mingwm10.dll | 运行库 | MinGW 运行时,线程模型相关 |
libgcc_s_dw2-1.dll | 运行库 | GCC 异常处理与底层支持 |
libstdc++-6.dll | 运行库 | C++ 标准库,字符串、容器等 |
看到QtNetwork4.dll基本就能确定,这套源码的网络层没有用裸 socket,而是走了 Qt 的QTcpServer和QTcpSocket。好处是跨平台,Windows 和 Linux 下改改编译参数就能跑;代价是 Qt4 本身比较老,信号槽还是字符串宏那一套,跟现在 Qt5/Qt6 的写法差别不小。
2.2 为什么选 Qt4 而不是 MFC 或纯 Win32
远程控制软件的核心难点不在界面,而在三件事:屏幕捕获、图像编码、网络传输。界面部分如果用 MFC,代码会被 Windows 消息循环绑死,想移植到 Linux 几乎要重写。Qt4 的QWidget加QPainter能把渲染层抽象出来,QImage直接承接屏幕位图,QTcpSocket负责传输,整个链路是干净的。
另一个原因是 MinGW。目录里mingwm10.dll和libstdc++-6.dll同时出现,说明编译工具链是 MinGW 而不是 MSVC。MinGW 编译出来的 exe 依赖这几个 dll,好处是体积小、不需要装 Visual C++ 运行库,坏处是换一台机器如果缺 dll 就直接报错。常见做法是把这几个 dll 和 exe 放在同一目录,Windows 会优先从当前目录加载。
提示:如果你打算重新编译,Qt4 的 qmake 工程文件里一般会写
QT += network,少了这行QtNetwork4.dll就不会被链接进去,运行时会提示找不到符号。
2.3 先跑通再读码,别一上来就啃源码
我的习惯是先让 exe 跑起来,确认通信链路是通的,再回头读代码。这样读的时候脑子里有画面,知道哪段代码对应哪个动作。具体步骤:
- 把压缩包解到同一个目录,确保
client.exe、cserver.exe和所有 dll 在同一层。 - 先双击
cserver.exe,观察是否有窗口弹出、是否提示监听端口。 - 再双击
client.exe,填入服务端 IP 和端口,点连接。 - 如果客户端能看到服务端桌面,说明整条链路没问题,可以开始读源码。
如果第 2 步就报错,大概率是缺 dll。用 Dependency Walker 或者直接看系统弹窗提示缺哪个文件,把对应的 dll 补到同目录即可。这一步看着简单,但很多人卡在这里就放弃了,以为是源码有问题,其实是运行库没放对。
3. 连接建立与屏幕传输:源码里最该先读的两段逻辑
3.1 服务端监听与客户端连接握手
远程控制的第一步永远是建立 TCP 连接。Qt4 里服务端用QTcpServer::listen,客户端用QTcpSocket::connectToHost。这段代码通常藏在cserver和client的 main 或者主窗口初始化里。我一般会先搜listen和connectToHost这两个关键字,定位到入口。
// 服务端:监听指定端口,等待客户端接入 QTcpServer *server = new QTcpServer(this); if (!server->listen(QHostAddress::Any, 8888)) { qDebug() << "listen failed:" << server->errorString(); return; } connect(server, SIGNAL(newConnection()), this, SLOT(onNewConnection())); // 客户端:发起连接,连接成功后触发 connected 信号 QTcpSocket *socket = new QTcpSocket(this); connect(socket, SIGNAL(connected()), this, SLOT(onConnected())); connect(socket, SIGNAL(readyRead()), this, SLOT(onReadyRead())); socket->connectToHost(serverIp, 8888);这段代码的逻辑很直白:服务端在 8888 端口监听,有客户端进来就触发newConnection;客户端连上后触发connected,之后所有数据到达都走readyRead。参数上唯一要改的是端口号,8888 只是示例,实际用的时候建议换成一个不常被占用的端口,避免和别的服务冲突。
注意:Qt4 的信号槽连接用的是
SIGNAL()和SLOT()宏,字符串匹配在运行时完成,写错了编译不会报错,只会在控制台输出警告。这是 Qt4 时代最经典的坑之一。
3.2 屏幕捕获与图像编码的取舍
连接通了之后,服务端要不断把屏幕画面发给客户端。Qt4 里捕获屏幕一般用QScreen::grabWindow或者QPixmap::grabWindow,拿到一张QPixmap,再转成QImage做编码。编码方式决定了传输效率和画质,常见的有三种:
| 编码方式 | 带宽占用 | 画质 | 实现难度 |
|---|---|---|---|
| 原始 RGB 位图 | 极高 | 无损 | 最低 |
| JPEG 压缩 | 中等 | 有损,可调质量 | 低 |
| 差分编码 + 压缩 | 低 | 取决于差分算法 | 高 |
这套源码大概率用的是 JPEG 或者原始位图,因为代码量小,差分编码实现起来复杂。如果是 JPEG,Qt4 里用QImage::save到QBuffer,格式选"JPG",质量参数一般设 70 到 85 之间。质量太低画面糊,太高带宽扛不住。
// 捕获屏幕并编码为 JPEG QPixmap pixmap = QPixmap::grabWindow(QApplication::desktop()->winId()); QImage image = pixmap.toImage(); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "JPG", 75); // 75 是压缩质量,可按网络情况调整 // 发送时先发长度再发数据,避免粘包 QDataStream out(socket); out << (quint32)ba.size(); socket->write(ba);这里有个关键点:TCP 是流式协议,没有消息边界。如果直接write图像数据,接收端可能一次读到半张图,也可能读到两张图粘在一起。常见做法是先发一个 4 字节的长度头,接收端先读长度,再按长度读满数据。上面代码里的QDataStream就是干这个的。
3.3 输入同步:键鼠事件怎么从客户端传到服务端
客户端这边,用户在远程画面上点鼠标、敲键盘,这些事件要打包发给服务端,服务端再模拟出来。Qt4 里客户端重写mousePressEvent、mouseMoveEvent、keyPressEvent,把事件类型和坐标序列化后发走。服务端收到后,Windows 下用mouse_event和keybd_event模拟,Linux 下用 XTest 扩展。
// 客户端:把鼠标点击事件打包发送 void ClientWidget::mousePressEvent(QMouseEvent *event) { QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out << (quint8)0x01; // 0x01 表示鼠标事件 out << (quint16)event->x(); // x 坐标 out << (quint16)event->y(); // y 坐标 out << (quint8)event->button(); // 哪个键 socket->write(block); }参数上要注意坐标缩放。如果客户端窗口大小和服务端屏幕分辨率不一致,直接发原始坐标会导致点击位置偏移。常见做法是在客户端按比例缩放坐标,服务端再按自己的分辨率还原。这个细节很多简易远程控制源码会忽略,用起来就会觉得“点不准”。
4. 避坑与排查:这套 Qt4 远程控制源码最容易翻车的地方
4.1 双击 exe 提示缺少 dll
现象:双击client.exe或cserver.exe,弹窗提示“无法启动此程序,因为计算机中丢失 QtCore4.dll”或类似信息。
原因:MinGW 编译的 Qt4 程序依赖QtCore4.dll、QtGui4.dll、QtNetwork4.dll、mingwm10.dll、libgcc_s_dw2-1.dll、libstdc++-6.dll这几个运行库,系统 PATH 里没有,程序也不会自动去别处找。
解决:把这几个 dll 和 exe 放在同一个目录,Windows 加载器会优先搜索当前目录。如果还是缺,用 Dependency Walker 打开 exe,看红色标记的模块是哪个,补上即可。
4.2 连接成功但画面不刷新
现象:客户端显示“已连接”,但远程画面是黑的或者一直停在第一帧。
原因:服务端捕获屏幕后没有持续发送,或者发送线程被界面线程阻塞了。Qt4 里如果在主线程做屏幕捕获和编码,界面会卡住,定时器也触发不了。
解决:把屏幕捕获和发送放到单独的QThread里,用定时器控制帧率,一般 10 到 15 帧每秒就够了。帧率太高带宽扛不住,太低操作延迟明显。检查代码里是否有QTimer在驱动捕获循环,如果没有,需要自己加一个。
4.3 鼠标点击位置偏移
现象:客户端点某个按钮,服务端实际点到了旁边。
原因:客户端窗口尺寸和服务端屏幕分辨率不一致,坐标没有做缩放映射。
解决:在客户端记录远程屏幕的宽高,把鼠标事件坐标按比例换算成远程坐标再发送。服务端收到后直接用换算后的坐标模拟点击。如果源码里没有这个逻辑,需要在mousePressEvent和mouseMoveEvent里补上。
4.4 传输大图时程序卡死或断连
现象:画面复杂时客户端卡住,或者连接直接断开。
原因:一帧图像数据太大,write一次性写入导致缓冲区爆掉,或者接收端没有按长度读取,解析错位。
解决:发送端先发 4 字节长度头,再发图像数据;接收端先读 4 字节,确认长度后再循环读取直到读满。同时控制 JPEG 质量,网络差的时候降到 50 左右,牺牲画质保流畅。
4.5 服务端在 Win10 上模拟键鼠无效
现象:连接正常,画面正常,但服务端不响应键鼠操作。
原因:Windows Vista 之后mouse_event和keybd_event在某些权限下会被 UIPI 拦截,尤其是服务端没有以管理员权限运行,而目标窗口权限更高时。
解决:以管理员身份运行cserver.exe。如果还是不行,考虑改用SendInputAPI,它比mouse_event更底层,兼容性更好。这套源码如果用的是老 API,在 Win10 上翻车很正常。
5. 进阶用法:把这份源码改成能用的远程协助工具
5.1 加一层简单认证,别让谁都能连
原始源码大概率没有认证,连上就能控。这在局域网里玩玩可以,放到公网就是灾难。最简单的做法是在握手阶段加一个密码校验:客户端连接后先发一个密码哈希,服务端比对,不对就断开。
// 服务端:收到连接后先校验密码 void Server::onReadyRead() { QDataStream in(socket); QString password; in >> password; if (password != "your_password") { // 实际用哈希比对,别存明文 socket->disconnectFromHost(); return; } // 校验通过,进入正常画面传输流程 }密码别存明文,用QCryptographicHash算个 SHA256 再比。这一步不复杂,但能挡掉绝大部分误连和扫描。
5.2 用差分思路降低带宽
如果只是局域网用,JPEG 全量传没问题。一旦网络差一点,就得考虑只传变化区域。最简单的差分做法是:服务端保存上一帧图像,新帧和上一帧逐块比较,只把变化的块编码发送,客户端收到后把变化块贴到本地画面上。
这个改动量不小,但效果明显。我一般会先把屏幕分成 32x32 的块,比较每块的哈希值,变化的块才编码。客户端维护一个同样大小的画面缓冲,收到块数据后更新对应位置。这样静态画面下几乎不传数据,只有操作时才产生流量。
5.3 验证改动是否生效的三个检查点
改完代码别急着说“好了”,按这三个点验一遍:
- 连接阶段:用 Wireshark 抓包,确认握手数据里包含认证字段,错误密码确实被断开。
- 传输阶段:静态画面下观察网络流量,差分生效的话流量应该接近零。
- 操作阶段:在客户端快速拖动窗口,服务端画面跟随延迟应该在可接受范围内,一般局域网下 100ms 以内算正常。
我自己的习惯是每次改完网络层,都先用ping和netstat确认端口和延迟,再跑一遍完整连接流程。远程控制这东西,玄学问题不少,但只要把连接、编码、输入三条链路分开排查,大部分坑都能定位到具体哪一段。
从那以后我每次拿到这类源码包,都强制先跑通 exe、再抓包看协议、最后才动代码,顺序反了就容易在环境问题上耗掉半天。希望帮到你。
本文还有配套的精品资源,点击获取