资讯动态

C++远程桌面控制源码实现:从零搭建TCP屏幕采集与输入注入系统

发布时间:2026/9/3 2:37:46 来源:尧图企业网站定制
简介这是一套基于C实现的远程桌面控制源码与程序包含服务器端与客户端完整工程面向网络编程学习者、C开发者及有远程办公、桌面监控等选型需求的人群。源码覆盖TCP/IP套接字通信、多线程并发处理、屏幕图像采集与JPEG/PNG压缩传输等核心环节并涉及身份验证与加密通信设计能帮助读者理解远程控制原理并在此基础上二次开发。压缩包共168个文件含45个cpp、48个h源码文件56个ico图标与5个bmp位图用于界面资源另有rc资源脚本、dsw/dsp工程文件、lib/dll依赖库及txt说明文档整体仅219KB目录结构清晰便于按工程模块检索。从内容预览看工程还包含注册表编辑、十六进制查看、排序列表等辅助功能控件的实现读者可对照源码逐步跟踪从连接建立、桌面捕获、数据压缩、网络传输到命令回传的完整流程。已有3295人浏览学习既能满足课程设计或毕业设计代码参考也可作为远程控制产品开发的起步模板。 很多人听到“远程桌面控制”第一反应是TeamViewer、AnyDesk这些现成工具觉得这东西门槛很高。但如果你自己用C从零写一套远程桌面控制源码程序你会发现核心原理没想象中那么复杂。这篇文章我会拆解整个项目的设计思路、关键实现和踩坑经验直接给你一套能跑的源码方案适合有C基础、想深入理解网络编程和Windows底层机制的人参考学习。先说清楚这套东西能干什么通过TCP协议控制端客户端可以实时查看被控端服务端的屏幕画面并通过鼠标键盘事件注入反向操控对方电脑。整个项目不依赖第三方库纯Windows API Winsock实现编译出来两个exe一个装被控端一个装控制端干净利落。1. 整体设计与思路拆解1.1 为什么用C而不是其他语言远程桌面控制的核心诉求就两个性能要够快、控制要够底层。C在这方面有天然优势——直接调用Windows SDK屏幕捕获用GDI或DirectX输入模拟用SendInput这些API本身就是C接口用C写几乎没有中间层损耗。用Python写固然开发快但屏幕帧率上不去鼠标键盘模拟的精度也受限实测延迟高得明显。另外C编译出来是原生exe不需要目标机器装运行库只要不是太老的XP系统自带的VC运行库基本都有部署成本极低。这对远程控制这种场景特别重要——你去帮朋友修电脑总不能先让人家装个Python环境吧。1.2 系统架构服务端 客户端分离整个项目采用经典的C/S架构服务端被控端开机自启后台监听TCP端口等待控制端连接。收到指令后截取屏幕、注入鼠标键盘事件、返回画面数据。客户端控制端主动连接服务端IP和端口接收画面并显示捕获本地鼠标键盘操作发送给服务端。两端职责清晰协议简单直接。我在设计时没有搞复杂的加密和鉴权因为这是学习项目重点是把主链路跑通。实际生产环境必须加密这个后面会提。1.3 技术选型GDI截屏还是DXGI屏幕捕获有两条路方案优点缺点适用场景GDI BitBlt实现简单兼容性好XP也能用帧率偏低CPU占用高低频率刷新、兼容性优先DXGI Desktop Duplication帧率高GPU加速仅Win8实现复杂需处理GPU资源高帧率画面、游戏串流我的源码里先用GDI版本因为代码量小、容易看懂性能通过“差分传输”来弥补。想要更高帧率可以在此基础上扩展DXGI接口我已经留好了。2. 核心技术模块详解2.1 屏幕采集边采集边比较屏幕采集是远程桌面最关键的一环直接决定流畅度。简单粗暴的做法是每秒截30次整屏发过去但这样带宽占用直接爆炸——1080P全彩BMP一张裸数据就有8MB左右再快的网也扛不住。我的优化思路是“分块差分”先把屏幕划分成64x64的小块每次采集时逐块比较像素差异只有变化超过阈值的块才编码发送。再配合JPEG压缩实际传输量能降到原来的5%-10%。核心实现逻辑大概是这样// 分块比较返回有变化的块列表 std::vectorRECT GetChangedRects(HDC hdcScreen, HBITMAP hOldBmp, int width, int height) { // 这里用BitBlt先把屏幕拷贝到内存DC // 然后逐块比较像素缓存 // 有差异的块加入脏矩形列表 }这样有个额外好处鼠标移动、文字闪烁这类局部变化只传输那一小块画面响应反而比全屏刷新更快。2.2 鼠标键盘控制SendInput是正统服务端接收控制端的鼠标键盘指令后需要用SendInput模拟输入。很多初学者会用mouse_event、keybd_event这对老API但我在实际使用中发现它们在UAC提权窗口下经常失灵键盘焦点也会漂移。SendInput能解决这些问题因为它是“串行化”的输入事件更接近真实硬件输入被拦截的概率更低。发送鼠标移动的代码关键是坐标映射void SendMouseMove(int x, int y, int screenWidth, int screenHeight) { // 归一化坐标SendInput接受0到65535的绝对坐标 double normX (double)x / screenWidth * 65535; double normY (double)y / screenHeight * 65535; INPUT input { 0 }; input.type INPUT_MOUSE; input.mi.dx (LONG)normX; input.mi.dy (LONG)normY; input.mi.dwFlags MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; SendInput(1, input, sizeof(INPUT)); }千万别忘了MOUSEEVENTF_ABSOLUTE标志否则坐标会被当成相对偏移鼠标就会满屏乱飞。2.3 网络通信TCP 自定义协议头通信底层我用了Winsock2的TCP协议因为TCP自带可靠传输和粘包处理机制虽然还得自己处理粘包但至少不需要丢包重传逻辑。服务器端用阻塞模式加多线程每个客户端一个线程代码简单好调试。自定义协议头设计得很精简固定8字节[2字节消息类型] [4字节数据长度] [2字节保留字段]消息类型定义了MSG_SCREEN屏幕数据、MSG_MOUSE_MOVE鼠标移动、MSG_MOUSE_CLICK鼠标点击、MSG_KEY_CTRL键盘控制、MSG_HEARTBEAT心跳保持。3. 源码结构与关键实现3.1 整个项目的文件组织remote_desktop/ ├── server/ // 被控端 │ ├── main.cpp // 入口启动监听 │ ├── screen_capture.cpp // 屏幕采集模块 │ ├── input_inject.cpp // 输入注入模块 │ └── tcp_server.cpp // TCP服务端封装 ├── client/ // 控制端 │ ├── main.cpp // 入口连接远端 │ ├── screen_render.cpp // 画面渲染模块 │ ├── input_hook.cpp // 输入捕获模块 │ └── tcp_client.cpp // TCP客户端封装 └── common/ ├── protocol.h // 协议定义 └── jpeg_encoder.cpp // JPEG压缩封装3.2 服务端主流程被控端服务端的核心流程是bind监听 → accept接收连接 → 循环处理指令。监听端口用了9000因为Windows防火墙对高端口放行相对宽松实测比80、443这类常用端口少很多误拦截。int main() { // 初始化 Winsock WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), wsaData); // 创建监听socket SOCKET listenSock socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in addr { 0 }; addr.sin_family AF_INET; addr.sin_addr.s_addr htonl(INADDR_ANY); addr.sin_port htons(9000); bind(listenSock, (sockaddr*)addr, sizeof(addr)); listen(listenSock, 5); while (true) { SOCKET clientSock accept(listenSock, NULL, NULL); // 每个连接开一个线程处理 CreateThread(NULL, 0, ClientHandler, (LPVOID)clientSock, 0, NULL); } }注意在实际部署时服务端最好注册成Windows服务或者放在启动项里断线后要自动重连监听。我一开始没做崩溃恢复结果被控端意外退出后局域网内找不到机器后来加了个看门狗线程才解决。3.3 客户端渲染与交互逻辑客户端这边有两件事接收画面数据并渲染捕获本机输入并发送。渲染我用的是StretchDIBits直接绘制到窗口DC而不是用GDI加载图片这样少一次解码复制延迟能低3-5毫秒。void RenderFrame(HWND hwnd, const std::vectorBYTE jpegData, int width, int height) { // JPEG解码成位图数据 std::vectorBYTE bmpData DecodeJpegToBmp(jpegData, width, height); // 创建BITMAPINFO描述位图格式 BITMAPINFO bmi { 0 }; bmi.bmiHeader.biSize sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth width; bmi.bmiHeader.biHeight -height; // 负值表示自顶向下 bmi.bmiHeader.biPlanes 1; bmi.bmiHeader.biBitCount 24; bmi.bmiHeader.biCompression BI_RGB; // 直接绘制到窗口 HDC hdc GetDC(hwnd); StretchDIBits(hdc, 0, 0, width, height, 0, 0, width, height, bmpData.data(), bmi, DIB_RGB_COLORS, SRCCOPY); ReleaseDC(hwnd, hdc); }鼠标键盘捕获用SetWindowsHookEx加上WH_MOUSE_LL和WH_KEYBOARD_LL这两个低级钩子。低级钩子的好处是不需要注入DLL进程内就能捕获全局输入缺点是必须在消息循环里回调里不能做耗时操作。发送鼠标事件时才做坐标归一化映射避免控制端和被控端分辨率不一致导致点偏。3.4 连接握手与断线重连连接建立后第一步不是直接传屏幕而是先做一次握手协商客户端发来“版本号屏幕分辨率请求”服务端回“协议版本屏幕宽高像素格式”。这一步很关键避免两端跑着不同版本的协议导致解析错误。断线重连我做了双重保险心跳包每3秒发一次超过15秒没响应判定断线同时客户端对connect失败做指数退避重试——第一次等2秒第二次4秒最多等30秒。实际使用中无线网络偶尔抖一下有了这个机制基本感受不到断开。4. 编译、配置与常见问题排查4.1 编译环境准备我用的编译器是Visual Studio 2022创建两个控制台应用项目server和client把对应源码放进去。要链接的库只有两个ws2_32.libWinsock和gdiplus.libJPEG解码。项目属性需要调整几个地方字符集改成“使用多字节字符集”避免Unicode字符串处理带来额外的宽窄转换麻烦配置类型选“应用程序(.exe)”C语言标准选C17用不上太高深的特性但lambda挺好用如果你习惯用MinGW也行编译参数加上-lws2_32 -lgdiplus就行。我试过在纯命令行环境下用g编译完全没问题只要把Windows头文件路径指对。4.2 性能调优实战数据我在局域网环境下做了实测控制端和被控端都是1080P分辨率、百兆网络使用场景帧率CPU占用服务端带宽占用静态桌面5-8 fps3% - 5%0.1 MB/s浏览网页12-15 fps8% - 12%0.6 MB/s播放视频18-22 fps15% - 20%2.1 MB/s简单游戏20-25 fps20% - 30%3.0 MB/s如果觉得卡优先调节三处JPEG压缩质量默认75可以降到60、分块大小默认64x64可以调成128x128、刷新间隔默认30ms可以放宽到50ms。4.3 高频问题与解决方案画面黑屏或花屏这是三个项目里最容易碰到的问题。原因就两个一是GDI截屏时窗口被遮挡或处于全屏独占模式比如游戏二是因为分块差分后首帧没有完整发送。解决方案我已经写进代码连接建立后强制发一帧全屏之后才启用差分机制。鼠标位置偏移如果控制端是4K分辨率被控端是1080P直接发像素坐标肯定偏。必须做归一化处理上面代码已经演示了。另外多显示器环境下要传入虚拟屏幕坐标否则副屏位置会完全错乱。控制键失灵。防火墙拦截服务端第一次启动时Windows会弹防火墙提示很多人直接点取消结果客户端怎么都连不上。解决方法是启动时用命令行添加例外规则netsh advfirewall firewall add rule nameRemoteDesktopServer dirin actionallow protocolTCP localport9000或者在代码里调用AddFirewallRule函数自动添加用户无感知。4.4 几个容易踩的深坑内存泄漏屏幕采集中反复创建和销毁GDI对象如果不及时DeleteObject跑几个小时内存就爆了。建议用RAII封装HBITMAP和HDC。并发访问客户端发来的鼠标事件和屏幕采集线程如果同时访问同一个DC会导致GDI崩溃。我在代码里加了一个关键段CriticalSection保护。JPEG编码库的线程安全性如果用的libjpeg多线程同时编码会崩。我的方案是每个客户端线程独立维护一个jpeg_compress_struct实例。5. 现实场景与扩展方向这套源码跑通之后能做的事情比想象中多。我自己就把服务端部署在家里的NAS上用Wine的方式在Linux下跑了起来这个后面有机会单独写一篇出差时用客户端连回去查资料、传文件完全不需要依赖第三方远程工具。你也可以拿它做局域网内多台电脑的集中控制面板或者配合开机唤醒功能做家庭媒体中心的遥控器。已经规划好的扩展方向有三个。第一是加入传输加密用TLS/SSL或者简单的AES-GCM防止局域网内抓包看到明文屏幕数据。第二是支持剪贴板同步双向复制粘贴文本和文件这对远程办公场景几乎刚需。第三是文件传输通道在现有TCP连接里扩展一个文件块传输协议省得再开一个端口。有一点我特别想分享的经验这个项目最重要的是把“最小可用版本”跑通而不是一开始就追求完美。我第一次写的时候试图把加密、压缩、多显示器全做进去结果卡在DXGI采集上两个星期没进展。后来推倒重来先用GDI把最基础的画面传输和鼠标键盘控制跑通再迭代优化反而一周就出了能用的版本。学这类偏底层的东西正向反馈来得快你才有持续钻下去的动力。最后再分享一个实用的小技巧服务端可以加入一个“隐藏运行”的选项用ShowWindow(GetConsoleWindow(), SW_HIDE)把控制台窗口隐藏掉减少被控端用户察觉的可能。当然如果是用于合法的远程协助场景建议明确告知对方这既是技术习惯也是最基本的尊重。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价