Qt实现FTP文件传输:QNetworkAccessManager与QtFtp方案详解 📅 发布时间:2026/9/8 6:41:56 👁 浏览次数: 简介这套QT FTP文件传输源码包含两个完整的Qt工程myftp与myftp2面向需要集成FTP上传、下载、目录浏览等功能的C开发者。代码基于QFtp类完整演示了连接服务器、用户登录、上传下载文件、目录切换和创建等核心操作并利用信号槽机制监控传输进度、处理错误事件适合快速搭建FTP模块。压缩包共53个文件大小4.08MB主要包含cpp源文件、h头文件、ui界面设计、qrc资源文件以及sln、vcproj等工程配置还提供了编译好的exe与pdb调试文件可直接运行观察界面与传输效果。资源已有978人学习适合有一定Qt基础、希望掌握FTP通信实战技巧的开发者作为参考资料也可从中学习两个工程的差异并灵活复用。 直接用Qt写FTP文件传输这个需求我在实际项目里接过不止一次。别的不说光是在Qt5之后找不到QFtp模块这一下就能劝退一堆刚入坑的朋友。这篇文章就围绕“QT实现ftp文件传输代码”这个主题把我自己踩过的坑、试出来的可行方案、以及能直接抄的代码片段一次性讲清楚。不管是工控机上的固件升级、嵌入式设备里的日志回传还是局域网内两台电脑互传文件你都能从这套东西里找到能用的部分。先给结论如果你想快速落地用QNetworkAccessManager就够了如果项目里要列目录、断点续传、还能兼容老代码那建议直接把QtFtp模块拉进来编译。这两种路线下面的篇幅都会覆盖并且会解释清楚为什么这么选。1. 整体思路与方案选型1.1 为什么还在用FTP有人可能会问现在HTTP下载、网盘同步都这么成熟了干嘛还要自己写FTP客户端但你一旦做过设备端、工控端或者局域网内部工具就会发现FTP有它不可替代的位置。首先是协议简单、服务端好搭。Windows上开个IIS或者装个FileZilla ServerLinux下起个vsftpd几分钟就能有一个可用的传输通道不需要额外开发服务端。其次是跨平台能力稳定嵌入式Linux、Windows ARM这些环境里FTP客户端的C/C实现特别多移植成本低。还有一点是防火墙友好尤其在办公网环境里FTP的21控制端口加数据通道比自定义TCP端口更容易通过安全策略审批。于是“用Qt写一个FTP文件传输模块”就成了很多桌面工具、运维小助手的标配功能。它解决的问题非常明确用户选中文件点一下上传或者下载进度条能走断电能重连目录能浏览。这篇文章适合谁正在做Qt客户端开发、需要对接FTP服务器的开发者或者维护老项目时被迫接手QFtp代码的人。1.2 三条可行路线怎么选Qt里实现FTP传输我实际用下来主要是三条路线按推荐程度排个序。路线一QNetworkAccessManager。这是Qt Network模块自带的类不需要额外编译任何模块Qt5里直接支持ftp://协议用占位符URL形式ftp://用户名:密码主机/路径就能发起上传和下载。优点是零依赖、代码短、上手快缺点是功能有限列目录很别扭基本只能处理“上传文件”和“下载文件”两个动作而且Qt6里官方已经把FTP支持给移除了。所以如果是Qt5项目、传输逻辑简单这条最省事。路线二QtFtp模块。这里的QtFtp指的就是当年Qt4自带的QFtp类后来官方在Qt5里把它移出了主仓库放到了qttranslation类别的第三方维护仓库里通过源码方式提供。你需要下载qtftp源码用qmake或CMake自己编一个模块出来。编好之后就能用回经典的QFtp接口支持list()列目录、get()下载、put()上传、rename()重命名、remove()删除还能做断点续传。对于老项目迁移、或者功能要求完整的场景这是最舒服的方案。路线三集成libcurl。如果你本身项目里已经用了libcurl那顺手接FTP也非常成熟。libcurl对FTP协议支持最全主动模式、被动模式、断点续传、SFTP都能做。缺点是引入第三方动态库部署时会多一个DLL或SO的依赖。我的建议是Qt5 简单传输直接选路线一写代码半小时就跑通Qt5/Qt6 完整功能花钱花时间编译路线二项目里本来就有libcurl那就别折腾了走路线三。2. 工程搭建与测试环境准备2.1 Qt工程配置细节不管选哪种路线第一步都是把工程环境整好。先说用QNetworkAccessManager的情况pro文件里只需要加一行QT network然后包含头文件#include QNetworkAccessManager #include QNetworkRequest #include QNetworkReply如果你决定用QtFtp事情会多一点。我以“下载源码手工编译”的方式说明这样最可控。先把qtftp源码克隆到本地注意它的源码对应你当前Qt版本的分支比如5.15分支。然后在源码目录里用命令行编译mkdir build cd build qmake ../qtftp.pro mingw32-make编译完之后你会得到libQFtp.a或者.dll以及一堆头文件。把它放到Qt安装目录的lib和include路径下或者放到自己项目的3rdparty目录下pro文件里这样写QT network INCLUDEPATH $$PWD/3rdparty/qtftp/include LIBS -L$$PWD/3rdparty/qtftp/lib -lQFtp这里有几个注意点第一一定要确保编译器和Qt编译器版本匹配我用MinGW编过一次喂给MSVC的Qt工程链接的时候直接一堆undefined reference第二如果Qt路径里有空格部分老版本qmake会抽风建议源码和构建目录都放在纯英文路径下第三编译前先看一下README有些分支依赖Qt5::Network顺序写错会报找不到模块。2.2 搭建本地FTP测试服务器写代码之前我建议先把测试服务器搭起来。没有测试环境你连“目录列出来是空的还是不调用回调”都分不清排查问题会很难受。Windows上我最常用FileZilla Server。安装完设置一个监听端口默认21创建一个用户指定主目录勾选“写入”权限。如果你只是想快速验证Windows自带的IIS也可以开FTP站点但配置起来步骤略多而且对被动模式的支持有时会让人迷糊。Linux服务器上更简单一条命令的事sudo apt install vsftpd装完改一下/etc/vsftpd.conf确保这几项打开anonymous_enableNO local_enableYES write_enableYES local_umask022 pasv_enableYES pasv_min_port30000 pasv_max_port31000然后重启服务sudo systemctl restart vsftpd为什么要关注被动模式端口因为FTP有两种数据连接方式主动模式是服务器主动连回客户端的端口被动模式是客户端去连服务器开放的端口。办公网环境里客户端往往在NAT后面主动模式很容易失败所以我建议客户端一律使用被动模式同时把服务器的被动端口范围开出来。这一步后面排查“连接成功但列目录卡死”时会直接救命。3. 核心代码实现与解析3.1 用QNetworkAccessManager实现上传和下载先给一套能跑的完整代码。这个方案在Qt5.15下实测没问题Qt6就别期待了因为FTP协议支持已经被官方拿掉了。头文件里的定义#include QObject #include QNetworkAccessManager #include QNetworkReply #include QFile #include QUrl class FtpUploader : public QObject { Q_OBJECT public: explicit FtpUploader(QObject *parent nullptr); void upload(const QString localFile, const QString remoteUrl); private: QNetworkAccessManager m_manager; };实现文件#include FtpUploader.h #include QFileInfo FtpUploader::FtpUploader(QObject *parent) : QObject(parent) { } void FtpUploader::upload(const QString localFilePath, const QString remoteUrl) { QFile *file new QFile(localFilePath, this); if (!file-open(QIODevice::ReadOnly)) { qWarning() 本地文件打开失败: file-errorString(); return; } QUrl url(remoteUrl); url.setUserName(your_user); url.setPassword(your_password); QNetworkRequest request(url); QNetworkReply *reply m_manager.put(request, file); connect(reply, QNetworkReply::uploadProgress, this, [](qint64 bytesSent, qint64 bytesTotal) { qDebug() 上传进度: bytesSent / bytesTotal; }); connect(reply, QNetworkReply::finished, this, [reply, file]() { if (reply-error() QNetworkReply::NoError) { qDebug() 上传成功; } else { qWarning() 上传失败: reply-errorString(); } reply-deleteLater(); file-deleteLater(); }); }下载文件的写法几乎对称把put换成get然后通过QNetworkReply::readyRead或者finished信号把数据写进本地文件就行void FtpDownloader::download(const QString remoteUrl, const QString localFilePath) { QUrl url(remoteUrl); url.setUserName(your_user); url.setPassword(your_password); QNetworkRequest request(url); QNetworkReply *reply m_manager.get(request); QFile *file new QFile(localFilePath, this); if (!file-open(QIODevice::WriteOnly)) { qWarning() 本地文件创建失败; reply-abort(); return; } connect(reply, QNetworkReply::readyRead, this, [reply, file]() { file-write(reply-readAll()); }); connect(reply, QNetworkReply::finished, this, [reply, file]() { file-flush(); file-close(); if (reply-error() ! QNetworkReply::NoError) { qWarning() 下载失败: reply-errorString(); file-remove(); } else { qDebug() 下载完成; } reply-deleteLater(); file-deleteLater(); }); }这套代码里有个细节put的第二个参数可以直接传QIODevice*QNetworkAccessManager会自己读取文件内容并发送不用手动转成QByteArray这样大文件上传时内存占用不会暴涨。不过必须说清楚用QNetworkAccessManager做FTP你基本拿不到服务器返回的目录列表。它内部把FTP当成一种简单的HTTP变体来看待列目录并不是它的设计目标。如果你需要“打开界面就显示服务器上有哪些文件”这条路行不通。3.2 用QtFtp实现登录、列目录与下载真正常见需求其实是“能连上、能看到目录、能选择文件下载”这就要用QFtp了。我在这里给出一套经典调用模板当年从Qt4项目里一路用过来的。先包含头文件#include QFtp连接服务器并登录QFtp *ftp new QFtp(this); connect(ftp, QFtp::commandStarted, this, [](int id) { qDebug() 命令开始ID: id; }); connect(ftp, QFtp::commandFinished, this, [ftp](int id, bool error) { if (error) { qWarning() 命令失败: ftp-errorString(); } else { qDebug() 命令成功ID: id; } }); ftp-connectToHost(192.168.1.100, 21); ftp-login(your_user, your_password);登录之后列目录int listId ftp-list(/remote/path); // list结果会通过 listInfo 信号返回 connect(ftp, QFtp::listInfo, this, [](const QUrlInfo info) { qDebug() info.name() (info.isDir() ? [目录] : [文件]) info.size() 字节; });下载文件QFile *file new QFile(local_file.txt); file-open(QIODevice::WriteOnly); int getId ftp-get(/remote/path/remote_file.txt, file); connect(ftp, QFtp::commandFinished, this, [file, getId](int id, bool error) { if (id getId) { file-close(); if (error) { file-remove(); } } });上传文件类似把get换成put即可QFile *file new QFile(local_file.txt); file-open(QIODevice::ReadOnly); int putId ftp-put(file, /remote/path/remote_file.txt);这段代码里最关键的是理解QFtp的信号机制。所有命令都会返回一个int类型的ID然后你通过commandFinished(int id, bool error)里的id来判断是哪一个命令完成了。实际项目里经常有人犯一个错在commandFinished里不看id直接当作所有命令都成功了结果列表还没出来就开始下载逻辑全乱。我习惯的做法是用一个状态机或者命令队列把“连接 → 登录 → 列目录 → 下载”串起来。比如在commandFinished里根据当前状态决定下一步发什么命令而不是一股脑全部发出去。FTP命令本身是串行化的一个连着一个你同时发两个get后面的命令会排队但你想控制的顺序就会变得不可控排查起来特别痛苦。3.3 进度回调与界面联动无论是QNetworkAccessManager还是QFtp它们都有进度信号。前者是uploadProgress(qint64, qint64)和downloadProgress(qint64, qint64)后者是dataTransferProgress(qint64, qint64)。实际写界面时不要把进度信号直接跟UI控件连接因为频率可能很高界面会卡。我一般做一个简单的节流处理比如只有进度变化超过1%才更新进度条connect(reply, QNetworkReply::downloadProgress, this, [this](qint64 bytesReceived, qint64 bytesTotal) { if (bytesTotal 0) return; int percent static_castint(bytesReceived * 100 / bytesTotal); if (percent ! m_lastPercent) { m_lastPercent percent; emit progressUpdated(percent); } });这样既能看到实时进度又不会疯狂重绘。实测在Windows上频繁更新QProgressBar会占用不少CPU尤其是文件特别多、日志刷得特别快的时候。另外传输大文件时界面很可能卡死。QFtp本身是异步的但如果你的槽函数里做了大量同步文件读写一样会卡。我的解决办法是文件读写操作尽量在readyRead或dataTransferProgress中只做纯I/O不碰界面需要弹窗、刷新列表的逻辑放到主线程的事件循环里通过信号槽递交过去。4. 常见问题与排查技巧实录4.1 QFtp模块丢失导致编译失败这是我在Qt5时代接手老项目时遇到最多的坑。项目在Qt4下跑得好好的一到Qt5编译直接报error: QFtp file not found原因是Qt5官方把QFtp从核心模块中移除了。解决方案就是我前面提到的编译qtftp源码或者改用QNetworkAccessManager。另外一个隐蔽问题是即便你成功拿到了QFtp的源码编译也要注意模块版本和Qt版本严格匹配。我曾经用5.12的qtftp源码在5.15的Qt上编译链接虽然编译期没报错但运行时会随机崩溃查了很久才发现是ABI不匹配。后来老老实实切换到对应5.15分支问题立刻消失。4.2 中文文件名与乱码问题FTP协议传输文件名使用的字符集在不同服务器上并不统一。老的Windows FTP服务器默认用本地代码页比如GBK而Linux上的vsftpd默认用UTF-8。如果你客户端传上去的中文文件名在对面变成乱码或者在Windows上下载下来的中文名文件打开报错基本就是字符编码不匹配。QFtp里处理这个问题的方式比较直接。通过listInfo信号拿到的QUrlInfo::name()在Windows服务器场景下很可能需要做一次转换QString name info.name(); QString gbkName QString::fromLocal8Bit(name.toLocal8Bit());这个写法本质是把UTF-8的QString转到本地8位编码再读回来变相转成GBK。代码看起来绕但实际在Windows对接老FTP服务器时确实好用。Qt6里QString::fromLocal8Bit的行为有所调整建议在目标平台上写个小测试验证。如果你用QNetworkAccessManagerURL中的中文路径一般需要先做百分比编码QUrl url QUrl::fromUserInput(remotePath); url.setUserName(user); url.setPassword(pass);fromUserInput会自动处理不少转码细节比手动拼接字符串靠谱得多。4.3 连不上服务器与被动模式症状很典型connectToHost和login都返回成功但list或者get一直不回调或者直接超时报错“Failed to connect to server”。绝大多数情况下是主动/被动模式不匹配。FTP的主动模式下服务器会主动连接客户端的一个随机端口这在有防火墙的局域网环境里几乎必死。所以我一般无脑要求客户端使用被动模式。QFtp里通过setTransferMode设置ftp-setTransferMode(QFtp::Passive);QNetworkAccessManager没有直接暴露这个开关它默认会根据环境自动选择但实测在Windows防火墙开启时依然可能卡住。我的排查顺序是先用FileZilla客户端手动连接目标服务器看它是主动模式还是被动模式能通确认服务器侧已经开放被动端口范围客户端代码里显式设置被动模式如果还不行抓包看控制连接的响应常见错误比如425 Unable to build data connection基本就能锁定到防火墙上了。4.4 传完的文件打不开或“被截断”文件下载完成后打不开有一种隐蔽原因是FTP的传输模式被设置成了文本模式ASCII。在文本模式下客户端和服务器会做换行符转换一个二进制文件经过转换基本就废了。QFtp默认使用二进制模式但如果你在连接前后不小心调用了QFtp::Text就会踩坑。更常见的是文件大小不一致。QNetworkAccessManager下载时如果你在finished信号里直接file-close()而某些数据还在缓冲区里没写完就会出现文件不完整。保险起见在finished里调用file-flush()之后再关闭并且比较一下本地文件大小和服务器文件大小是否一致。另外断点续传也是文件传输绕不开的话题。QFtp的get支持QFtp::Resume参数int resumeGetId ftp-get(/remote/path/big.bin, file, QFtp::Resume);使用前需要先获取远端文件大小同时本地文件已经写入了对应长度的占位内容否则续传的位置会算错。我通常是在listInfo里拿到size()然后本地file-resize(size)再带着Resume标志发起下载。这套逻辑实测在弱网环境下能省不少重传流量。5. 还能怎么扩展SFTP与断点续传写完基本的FTP传输很多需求还会往前走一步加密。FTP明文的账号密码在公网上传简直是裸奔所以现在很多项目的下一步是支持SFTP基于SSH的文件传输协议。Qt本身没有原生的SFTP支持常见做法是集成libssh2或者QSsh。如果只是局域网内部用QFtp加被动模式已经够用但只要有“跨公网传输”这个需求我强烈建议直接上SFTP。libssh2的接口和QFtp风格差异不小但好在登录、读目录、下载文件这几个函数的抽象是一致的封装一层IFileTransfer接口底层分别实现FTP和SFTP业务代码基本不用大改。断点续传这块QFtp给了Resume标志但QNetworkAccessManager没有。如果项目固定走QNAM这条路续传就得自己动手先在本地文件末尾继续写同时发送Range请求头。部分FTP服务器支持REST命令QNAM内部不暴露这个命令所以建议要么换QtFtp要么换libcurl。就我个人经验来说手头这种“Qt实现FTP文件传输”的需求十有七八到最后都要被要求加上断点续传、目录同步、或者SFTP支持。所以一开始技术选型时别图省事选最简方案要留一点扩展空间。先用QNetworkAccessManager跑通原型没问题但正式立项时最好一步到位把QtFtp或者libcurl集成进去后面会轻松得多。最后再提醒一句测试环境和生产环境的FTP服务器行为经常不一致客户端代码千万别写死“主动模式”或者“被动模式”写成一个配置项部署的时候能改。这个小小的可配置设计能让你在客户现场少接无数个救急电话。本文还有配套的精品资源点击获取