news 2026/9/3 20:02:09

用Delphi从零开发远程控制程序:架构、源码与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Delphi从零开发远程控制程序:架构、源码与实战

简介:面向Delphi开发者的远程桌面(远程控制)程序源码资源,包含服务端与客户端双端实现,并局部借助C++辅助完成,适合正在学习网络通信、桌面控制或希望借鉴成熟控制逻辑的中高级Delphi爱好者参考,也可为远程运维工具开发提供工程蓝本。服务端主要负责屏幕图像采集与命令接收,客户端负责画面呈现与远程操作发送,两者配合可完成基本的远程控制闭环。代码当前已完成大部分功能,作者仍围绕算法进行迭代,核心亮点是采用分块算法替代传统隔行扫描方案,能够更高效地处理屏幕图像变化区域的捕获与传输,对理解远程控制底层原理颇有帮助。压缩包采用rar格式,大小约1.15MB,体量轻量便于快速下载阅读;目前已有749人学习使用。通过阅读源码,可以梳理远程连接建立、画面数据传输、客户端与服务端协作流程,以及分块算法相对于隔行扫描在画面变化捕捉、带宽占用和响应速度上的差异,尤其适合用于对比研究和项目二次开发。 说实话,当我决定用Delphi写一个远程桌面(远程控制)程序并整理成带源代码的项目时,周围不少人都觉得我在“复古”。毕竟现在的TeamViewer、向日葵、RustDesk一个比一个成熟,为什么还要自己造轮子?但实际做过之后你会发现,远程控制这个场景的需求远没有想象中那么统一:公司机房几十台老旧的Windows工控机要批量运维,内网环境不允许数据出网;客户现场的收银机需要远程协助,但对方又不想装一堆全家桶;还有一些特定行业要求控制端体积小、行为可预期、源码可控。这些时候,一个用Delphi自研的远程桌面程序反而比商业软件更合适——它只做你需要的功能,没有任何多余的东西。这篇文章就把我从零搭建这套远程控制程序的全过程、核心源代码模块以及实战中踩过的坑完整记录下来,给同样需要“自己动手做远程工具”的朋友一条可复现的路线。

1. 项目定位:为什么还要自研远程控制

1.1 远程控制程序的核心价值

远程桌面/远程控制程序从功能上讲,核心无非三件事:看对方屏幕、模拟鼠标键盘输入、传送文件。但“能做”和“好用”之间差距巨大。我这边整理下来,一个真正能落地使用的远程控制程序还需要满足几个隐藏需求:

  • 被控端能够无人值守地后台运行,开机自启、掉线自动重连。
  • 控制端画面流畅,延迟不能大到鼠标飞出去才看到画面回传。
  • 通信数据走自定义协议,不依赖第三方服务器中转,适用于局域网。
  • 代码可维护、可扩展,随时能在控制协议里增加新指令,比如远程运行某个命令、批量修改配置。

我的使用场景是为公司内部维护一批Windows工控机,系统涵盖WinXP到Win11,硬件配置普遍不高。在这种环境下,第三方远程工具要么版本太新跑不动,要么安全策略不符合内网规范,所以“用Delphi自研一个远程控制服务端+客户端”就成了最合理的方案。

1.2 为什么选Delphi而不是Python、C#或C++

很多人的第一反应是:现在写这类工具用Python不是更快吗?用C#不是写起来更轻松吗?确实,从“快速出原型”的角度,Python和C#都有优势,但真正做产品级工具时,Delphi有自己的独特位置。

Delphi编译出来是原生x86/amd64程序,单exe即可运行,不需要在目标机器上预装.NET Framework或Python解释器。这一点在维护老旧的Windows工控机时是致命优势——很多机器处于孤立网络环境,根本没法在线安装运行时。Delphi对Windows API的封装非常成熟,VCL中直接调用Win32 API、操作GDI、处理Socket都很方便,底层控制力和开发效率之间有一个很好的平衡。另外,Delphi 7时代的老代码到Delphi 10.4/11/12基本还能平滑迁移,这对已经有大量历史代码的公司来说非常有吸引力。

我并不是说Python或C#不好。如果你要处理服务器端的远程控制、跨平台支持,RustDesk那种Rust方案或者Python的Twisted框架都很出色。但针对“Windows平台、轻量、可控、不改现有网络环境”的局域网远程控制工具,Delphi依然是性价比很高的选择。

2. 整体架构设计与协议约定

2.1 控制端与被控端的两层结构

我采用的是最常见的两层结构:被控端叫Service端(或Server端),控制端叫Client端。Server运行在目标电脑上,后台监听TCP端口;Client运行在你的电脑上,主动连接Server。

之所以不用三层结构(加一个中心服务器做信令和转发),是因为我的场景全部在局域网内,直连延迟最低、部署最省事。但如果你的需求是跨互联网远程控制,那确实需要一台带公网IP的转发服务器,或者实现NAT穿透。我这次的方案把网络层抽象出来,后续要加中间服务器,改动范围也集中在连接建立部分,不影响核心的屏幕采集和指令处理逻辑。

2.2 自定义通信协议:文本指令头 + 二进制数据体

远程控制程序最忌讳的是把协议设计得过于复杂。我一开始参考过VNC的RFB协议,发现它虽然规范,但维护成本高,而且对Delphi这种快速开发场景来说有点大材小用。最终我自己定了一套简单的协议:每个数据包由指令头和指令体组成,指令头是文本格式,指令体是二进制格式。

协议包的格式约定如下:

指令名|参数1=值1|参数2=值2\n [二进制数据体]

举例:

GET_SCREEN|quality=70|width=1280|height=720\n MOUSE_MOVE|x=800|y=600\n MOUSE_CLICK|button=left|action=down\n KEY_EVENT|keycode=65\n FILE_PUSH|filename=report.txt|filesize=1024\n

文本头部方便调试和扩展,数据体单独用二进制流传输,比如JPEG图像数据、文件内容。每条命令以换行符结尾,收到完整的一行后,通过指令名分发到对应处理函数。这种简单协议在实际运行中非常稳定,而且我能在telnet环境里手动模拟指令来排查问题,这一点对调试帮助巨大。

2.3 模块划分:五个核心功能单元

从代码组织上,我把整套程序拆成五个模块:

模块职责关键实现
ScreenCapture抓取屏幕图像并压缩GDI BitBlt + JPEG编码
InputSimulator模拟鼠标键盘输入SendInput / SetCursorPos
FileTransfer双向文件传输分包发送 + CRC校验
NetworkCoreTCP通信与协议解析Indy TIdTCPServer / TIdTCPClient
SessionManager认证、权限与连接状态管理随机Token + 心跳保活

这样拆分的好处是,每个模块可以单独测试。比如ScreenCapture模块可以先用一个定时器把屏幕存成本地JPEG文件,验证图像质量;NetworkCore模块可以先做文本聊天功能,跑通了再叠加图像传输。模块边界清晰之后,代码写起来顺手,排错效率也高。

3. 核心源代码模块逐段拆解

3.1 屏幕截取与图像编码

屏幕截取是整个远程控制程序里最基础也最关键的环节。我早期用过一个简单的方案:直接整个屏幕截成BMP,然后转成JPEG发送。这在1080p分辨率下勉强能用,但网络稍差就明显卡顿。

最终采用的方案是GDI的方式,获取屏幕DC后通过BitBlt复制到位图,再交给JPEG编码器压缩。代码核心如下:

function CaptureScreenRect(SrcRect: TRect; var Bmp: TBitmap): Boolean; var DC: HDC; MemDC: HDC; BMPHandle: HBITMAP; W, H: Integer; begin Result := False; W := SrcRect.Right - SrcRect.Left; H := SrcRect.Bottom - SrcRect.Top; DC := GetDC(0); MemDC := CreateCompatibleDC(DC); BMPHandle := CreateCompatibleBitmap(DC, W, H); try SelectObject(MemDC, BMPHandle); if BitBlt(MemDC, 0, 0, W, H, DC, SrcRect.Left, SrcRect.Top, SRCCOPY) then begin Bmp := TBitmap.Create; Bmp.Handle := BMPHandle; Bmp.PixelFormat := pf24bit; Result := True; end; finally DeleteObject(BMPHandle); DeleteDC(MemDC); ReleaseDC(0, DC); end; end;

注意几个细节。第一,PixelFormat要设置成pf24bit,而不是默认的pfDevice,否则在某些显卡驱动下保存JPEG会出现色彩失真。第二,截取区域不要直接写死全屏,而是预留一个SrcRect参数,这样后续做“局部刷新”优化时不用改动函数接口。第三,抓屏后应立即释放DC和位图句柄,否则长时间运行会逐渐耗尽GDI对象,导致被控端屏幕闪烁甚至系统异常。

JPEG压缩我直接用了VCL自带的TJPEGImage。这里有个经验值:remote assistance场景下JPEG质量设为70到80之间最合适,画质损失肉眼基本不可见,但体积比无损BMP小一个数量级。1080p屏幕上,质量75的JPEG压缩后普遍在30到80KB左右,局域网千兆环境下传输延迟可以控制在50ms内。

3.2 网络传输与粘包处理

远程控制的数据传输走的是TCP长连接。网络库我用的是Indy的TIdTCPServer和TIdTCPClient,因为Delphi集成度高,处理SSL和线程模型比较省心。TCP是流式协议,没有天然的“消息边界”,所以必须自己处理粘包和半包问题。

我的解决方案是严格按照“长度前缀”来读取数据包:自定义一个包头,包含指令长度、数据体长度等信息,然后按长度精确读取。代码结构如下:

procedure TNetworkCore.SendPacket(ACommand: string; AData: TStream); var Header: string; HeaderBytes: TBytes; DataLen, HeaderLen: Integer; begin DataLen := 0; if Assigned(AData) then DataLen := AData.Size; Header := Format('%s|datalen=%d'#10, [ACommand, DataLen]); HeaderBytes := TEncoding.UTF8.GetBytes(Header); FIOHandler.Write(HeaderBytes, Length(HeaderBytes)); FIOHandler.WriteBuffer(AData, DataLen); end;

接收端读取时,先按行读取Header,解析出datalen,再精确读取DataLen字节的数据体。这样不管发送端发送多快、TCP怎么分包都不怕,每个包都能被完整还原。

这个模块我踩过最深的一个坑是:不要在Indy的OnExecute事件里直接读取大块图像数据并同步执行JPEG解码和屏幕绘制。因为Indy的OnExecute跑在独立的线程里,如果一个连接线程被阻塞,其他连接也会受影响。后来我改成接收线程只负责把数据包放入线程安全队列(TThreadedQueue),UI线程轮询队列并更新画面。实测性能提升非常明显,尤其是多客户端同时连接时稳定性大幅改善。

3.3 鼠标键盘事件注入

控制端发过来的鼠标坐标和按键动作,最终要落到被控端的真实输入上。早年Delphi程序里很多人用mouse_event和keybd_event,这两个API简单,但问题是它走的是旧式输入机制,在UAC权限提升的窗口、安全桌面等场景下会失效。更现代的做法是使用SendInput。

下面是我在InputSimulator模块中使用的鼠标点击核心代码:

procedure SendMouseButton(AButton: TMouseButton; AIsDown: Boolean); var Inp: TInput; begin FillChar(Inp, SizeOf(Inp), 0); Inp.Iype := INPUT_MOUSE; case AButton of mbLeft: if AIsDown then Inp.mi.dwFlags := MOUSEEVENTF_LEFTDOWN else Inp.mi.dwFlags := MOUSEEVENTF_LEFTUP; mbRight: if AIsDown then Inp.mi.dwFlags := MOUSEEVENTF_RIGHTDOWN else Inp.mi.dwFlags := MOUSEEVENTF_RIGHTUP; end; SendInput(1, Inp, SizeOf(Inp)); end;

键盘模拟同理,也是通过SendInput发送KEYDOWN和KEYUP事件。这里要特别注意一个坐标问题:被控端如果开启了DPI缩放,控制端传过来的坐标必须经过DPI换算,否则在高DPI显示器上鼠标位置会偏得离谱。我在控制端发送鼠标坐标前,会先获取被控端屏幕的逻辑分辨率和物理分辨率,按下述公式换算:

真实坐标X := 物理像素坐标X * (逻辑分辨率宽度 / 物理分辨率宽度)

否则你在自己2K屏幕上点得欢快,对端的1080P显示器上光标可能跑到右下角去,完全是“看不见的坑”。

4. 实测效果、性能优化与排障纪实

4.1 局域网内实测表现

我在公司内网里部署了一套,控制端和被控端都走千兆以太网,被控端是一台i5-6500处理器、8GB内存的普通办公机,系统为Windows 10专业版,而控制端是一台较新的移动工作站。

测试项目实测结果
1080P全屏截图耗时12ms ~ 25ms
JPEG压缩耗时(质量75)15ms ~ 30ms
单帧图像大小35KB ~ 90KB
局域网端到端延迟45ms ~ 80ms
每秒刷新率约 15 ~ 20 帧

这个流畅度对于运维和远程协助完全够用。如果网络环境差一些,比如Wi-Fi或跨交换机,我通常把JPEG质量降到60,再限制发送频率为每秒10帧,体验也还行。关键不是追求高帧率,而是保证画面的“可读性”和操作的“确定性”。

4.2 常见问题排查速查表

在开发和内部测试阶段,我遇到了不少问题,这里整理成速查表,直接给你参考:

现象可能原因解决方案
控制端连接被拒服务端未启动、端口被防火墙拦截查看服务端监听状态,放行TCP端口;不要忘了服务端也要允许入站规则
连上了但黑屏屏幕抓取权限不足、Session 0隔离、JPEG解码失败试验以管理员身份运行服务端;或改用服务模式与桌面交互
鼠标坐标偏移DPI缩放导致逻辑坐标与实际像素不一致按DPI系数换算坐标;在程序里调用SetProcessDPIAware
画面卡顿、延迟飙升JPEG质量太高、带宽不足、发送频率无限制降JPEG质量、限制帧率、启用差异传输
服务端长时间运行崩溃GDI对象泄漏、句柄未释放监控GDI句柄数量,确保每次截图后释放DC和Bitmap
粘包导致指令解析错乱自定义协议未处理TCP长度边界严格采用长度前缀方案,用ReadLn读取头部,再按长度读数据体

4.3 性能优化三板斧

这套程序我先后改过三轮,最有效的优化其实就三招。

第一招,JPEG质量动态调节。在控制端根据最近5帧平均传输时间,动态决定服务端下次截图的压缩质量。网络好时用85,网络差时降到55,整个过程完全自动。

第二招,局部刷新。屏幕变化往往只占一小块区域,比如鼠标移动、输入框跳动。我在服务端保留上一帧的完整位图,截取新图后计算变化区域,只把变化区域裁剪出来压缩发送。这个优化在“阅读文档、写代码”这类静止画面场景下,能把带宽占用降一个数量级。

第三招,控制端画面缩放。如果被控端的屏幕分辨率比控制端大,等比例缩小显示,而不是让画面超出窗口。缩放后不仅看得更全,还能降低控制端解码和绘制的负载。

5. 权限、安全与使用边界

5.1 认证与加密

远程控制程序天然涉及敏感操作,如果没有认证和加密,等于把一台电脑的大门敞开给内网里的任何人。我在这套程序里加了两层防护。

第一层是连接认证。客户端连接服务端时,服务端生成一个随机挑战码发给客户端,客户端使用预共享密钥对挑战码做HMAC计算,返回校验值,服务端比对通过后才允许后续通信。这个过程保证了即使有人抓包,也无法直接重放认证数据。

第二层是传输加密。由于这套工具主要跑在可信内网,我暂时用了轻量级的流加密方式对图像数据和指令做混淆通信,避免明文传输。如果后续要跨公网使用,我会再接入TLS,Indy本身支持TIdSSLIOHandlerSocketBase,改动可控。

5.2 合规使用提醒

这一点我必须明确写在文章里:远程控制工具只能用于你自己拥有合法管理权限的设备,比如你自己管理的机房服务器、家里几台电脑、客户明确授权的运维项目。未经授权控制他人电脑,无论是出于好奇还是其他目的,都是违法行为。

我在被控端做了两个安全设计:一是连接时被控端屏幕右下角会弹出一个白名单提示图标,让现场的人知道当前正在被远程控制;二是在服务端配置里增加了IP白名单,只有允许的IP段才能发起连接。这两个设计不复杂,但能避免很多不必要的麻烦,也符合企业内部安全审计的要求。

5.3 兼容性经验小结

最后聊一下Delphi版本兼容性。我的代码全程用VCL原生组件,没有依赖第三方控件,从Delphi 7到Delphi 11/12的主分支都能编译通过。唯一需要留意的是Indy版本差异——老Delphi默认带Indy 10.0/10.1,新版本带Indy 10.6,API基本兼容但Socket选项定义略有不同。如果遇到编译错误,优先检查IOHandlerType和ReadTimeout的设置,这是版本差异最容易暴露的地方。

我自己维护这套源码的习惯是:主分支放在Delphi 10.4上,因为原生的HighDPI支持最好;同时保留一个Delphi 7兼容的branch,用来给老设备临时编译特殊版服务端。这种双分支的做法,让我在维护老旧Windows时少了很多折腾。

写在最后

从最初的“只想快速跑通抓屏”,到后来不断加功能、优化传输、完善安全性,这套远程控制程序前前后后折腾了一个多月。回头看来,最有价值的不是代码本身,而是对Windows底层图形机制、Socket编程和输入模拟API的理解全都落到了实处。如果让我给同样想自己写远程工具的人一句忠告,那就是:先把“抓屏+传图+点击”这个最小闭环跑通,再考虑花里胡哨的功能。等你真正把这三件事做好,远程控制也就完成了一大半。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 19:57:18

金属表面缺陷检测实战:YOLOv8+PySide6桌面工具开发

金属表面缺陷检测这件事,真正落地时最麻烦的不是模型选型,而是把检测能力装进一个操作人员愿意用的桌面工具里。用 YOLOv8 或 YOLOv5 做目标检测,再用 PySide6 写界面,是目前比较常见的组合:模型负责从图像里找到划痕、…

作者头像 李华
网站建设 2026/9/3 19:54:35

Forge 1.20.1模组开发:实现给鸡挤奶与混沌碎片合成

在一档名为《石头世界》的系列内容第 4 季第 14 集里,“给鸡挤奶?还要制作成混沌碎片?”听起来像一句整活台词。但把它当成玩法需求交给 Minecraft 模组开发者时,这句话包含的信息量并不小:玩家要和鸡交互、交互结果要…

作者头像 李华
网站建设 2026/9/3 19:52:31

去吧皮卡丘怀旧版二转Mega版本测试要点解析

这次我们来看一个正在测试中的版本:去吧皮卡丘怀旧版二转 Mega 版本。这个版本的关注点不是新增了多少只新精灵,而是把怀旧养成线重新整理了一遍——二转条件、Mega 进化门槛、材料消耗、精灵强度曲线都有明显调整。如果你正准备回归怀旧玩法&#xff0c…

作者头像 李华
网站建设 2026/9/3 19:51:12

ControlFLASH V15.02.00安装与PLC固件升级实战指南

简介:ControlFLASH_V15.02.00_Install.zip 是 Rockwell Automation 官方推出的固件更新工具安装包,面向负责 Allen Bradley PLC、驱动器等自动化设备维护的工程师,用于安全升级设备固件、修复缺陷并提升性能。包内含472个文件,约9…

作者头像 李华
网站建设 2026/9/3 19:46:31

Workerman+ThinkPHP5实现在线客服系统:长连接架构与实战解析

简介:面向需要部署在线客服系统的 PHP 开发者与运维人员,这是 Workerman 在线客服系统安装部署资源包,围绕 Nginx 1.21.4 PHP 7.2 MySQL 5.7.40 环境展开,以课程资源加软件插件形式整理,重点解决源码上传、解压、数据…

作者头像 李华
网站建设 2026/9/3 19:46:22

RAG投毒致注意力崩溃,文档级注意力是关键检测信号

RAG 投毒这套攻击思路,凡是做过知识库问答的人应该都不陌生。但我第一次真正意识到“投毒能打崩注意力”时,并不是因为模型输出了明显有害的话,而是因为一次很普通的问答:用户问某个政策条款,检索系统返回了三篇文档&a…

作者头像 李华