尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
QT之FTP上传下载工具源码解析:选型、实现与避坑指南
简介这是一份基于QT5框架的FTP客户端功能工具源码面向需要在Windows、Linux及嵌入式Linux等平台实现FTP文件上传与下载的C开发者。项目通过QNetworkAccessManager与QFtp类的配合封装了FTP服务器连接、用户登录、目录浏览、文件传输等核心流程免去开发者单独下载编译QFtp类的繁琐步骤可直接集成到现有QT工程中快速使用适用于工业控制、嵌入式设备数据同步及局域网文件管理等多种场景。 资源压缩包共包含12个文件以cpp源码、h头文件和ui界面文件为主另有工程配置信息及两张目录/文件图标示意图片整体包体仅32KB结构紧凑清晰。核心代码覆盖FTP下载与上传调用、URL地址解析处理和界面布局设计便于对照学习完整QT项目的模块划分、信号槽衔接与界面交互组织方式。 目前已有1097人浏览学习。对研究QT网络编程或需快速搭建FTP工具的开发者而言该源码提供了可运行的基础框架涵盖下载、上传的完整示例并支持传输进度监控与状态实时反馈。借助QT的信号与槽机制可深入理解FTP会话管理、异常处理与界面更新思路并能在其基础上扩展FTPS或SFTP加密传输掌握跨平台网络应用的实际开发模式。1. 这个QT之FTP上传下载工具源码解决的是谁的痛点拿到一份“QT之FTP上传下载等功能工具源码”时我第一反应不是看界面长什么样而是看它用的是哪条FTP技术路线。因为Qt官方对FTP的支持在版本更迭中被砍过、移过、改过路线选错了后面所有二次开发都是给自己挖坑。这类工具解决的是典型的内网文件流转问题设备固件批量分发、配置文件远程更新、产测工位上传测试结果。对这些场景来说现成的FTP客户端软件能完成任务但没法嵌入自家业务流程而“源码”的价值就在于可以改造成带进度条、带任务队列、带断点续传的定制化工具。读者通常分两类一类是刚接触Qt想找个能跑的FTP例子改改界面另一类是手头已经有一份老源码编译不过、乱码、大文件崩溃需要知道坑在哪。这篇笔记按后者的视角写但前一条路也覆盖从选型到骨架再到四个核心功能最后给断点续传和自测技巧。如果你只是要一个能用的内网小工具看完第二、三章基本就能决定值不值得动手。如果你想改造源码第四章和第五章是重点。2. 实现路线选型QFtp、QNetworkAccessManager与libcurl怎么选FTP协议本身不复杂控制连接走命令数据连接走文件内容难点在于两个连接怎么协同、被动模式和主动模式怎么切、断点续传怎么发偏移命令。Qt生态里做FTP有三条常见路线每条路线的维护状态、坑的数量、二次开发成本完全不一样。拿到源码先翻它的include和网络相关目录确认它是哪条路线再谈怎么改。2.1 QFtp老源码的最大门槛是它已经不在官方包里如果你看到的源码里有#include QFtp或者QFtp *ftp new QFtp(this);恭喜你拿到的是Qt4时代的代码。QFtp这个类在Qt4年代确实是最好用的FTP接口login、list、get、put、removeDir都有对应的槽和信号写起来像在操作本地文件系统。但从Qt5开始官方包不再包含QFtp它被挪到社区维护的独立模块里编译时需要自己把模块源码拷进工程或者修改.pro文件去引用。更麻烦的是Qt6之后官方连单独的模块都不再跟进QFtp的信号槽机制在新版Qt上编译会碰到moc和元类型注册的兼容问题。所以我在判断一个“QT之FTP上传下载等功能工具源码”值不值得用时第一眼只看一件事QFtp出现则谨慎QNetworkAccessManager出现则可以继续看。老源码如果业务逻辑不复杂把QFtp调用换成QNetworkAccessManager的请求不是很难但前提是你愿意动网络层代码而不是只改几个按钮。2.2 QNetworkAccessManagerQt5时代的默认FTP答案QNetworkAccessManager是Qt5里默认的网络入口虽然大家平时拿它发HTTP请求但它在Qt5中确实保留了FTP协议支持。用起来和HTTP几乎一样把URL设为ftp://协议登录信息放进URL里然后发get或put请求。它内部自己管理控制连接和数据连接会话层面的PASV、PORT、RETR、STOR都由后端处理使用者不用碰协议细节。这是大多数“QT之FTP上传下载等功能工具源码”选择的路线也是我通常会优先推荐的路线。这条路线有一个明确边界只有Qt5适用。Qt6版本的QNetworkAccessManager已经不再支持ftp协议如果项目已经升级到Qt6就不能指望这条路线需要转第三方库。另外QNetworkAccessManager对FTP的控制粒度比较弱比如主动/被动模式它不会给你单独开关断点续传要靠请求头的偏移设置这些细节后面单独讲。整体判断就是源码基于Qt5、场景是内网工具、不需要FTPS加密直接用这条路线二次开发是最省事的。2.3 libcurl路线什么场景才值得引入第三方库当需求里出现“要支持FTPS”“要兼容SFTP”“要同时对接多个FTP服务器且各自被动端口范围不同”QNetworkAccessManager就有点扛不住了。我一般会选libcurl封装路线。libcurl是成熟的命令行工具库FTP、FTPS、SFTP都支持CURLOPT_* 选项几百个从被动模式开关到传输模式都能精确控制。Qt代码里封装libcurl通常做法是写一个FtpTransfer类内部持有CURL句柄通过信号把进度和完成状态抛给界面。代价也很明显需要处理第三方库的编译和分发。libcurl依赖OpenSSL或者系统TLS库换一台机器可能缺DLL或so文件。这就是为什么我在多数情况下不会轻易引入libcurl除非需求里确实需要加密传输或跨协议支持。对一份以“上传下载工具”为目标的源码来说先确认它有没有引入libcurl相关头文件如果有还要看它的依赖项是否随项目一起提供了否则编译通过也只能在自己机器上跑。2.4 三种方案对比与我的选型习惯用一个表来收束选型会清晰很多。这里不列空洞的优缺点只写对二次开发有实际影响的维度。路线API易用性断点续传支持FTPS/SFTP支持Qt6兼容二次开发成本QFtp / QtFtp模块高信号槽直观需要自己处理REST偏移不支持编译困难源码改造工作量大QNetworkAccessManager中按HTTP风格调部分支持看后端翻译不支持不支持ftp协议适合多数内网工具libcurl封装低需管理CURL句柄完整支持支持兼容良好需要解决依赖分发我的选型习惯很简单源码本身是QNetworkAccessManager路线需求又不超过内网和明文FTP就坚持用下去不要因为某个功能实现费劲就想着换库。源码本身是QFtp老代码先评估QFtp调用数量Qt5项目直接把网络层替换成QNetworkAccessManagerQt6项目就直接跳到libcurl封装。真正决定要不要换路线的不是好不好写而是目标机器上能不能跑起来。FTP工具最终要丢到甲方内网或者产线机器上依赖越少越可靠。提示拿到源码先别急着编译花十分钟确认网络层用的是哪条路线能省下之后大部分排错时间。3. 源码骨架搭建FtpClient类怎么拆分UI与网络逻辑确认路线之后下一步是看源码的工程结构。实用性强的FTP工具源码界面和网络逻辑一定是分开的而不是把上传下载代码直接写在按钮的槽函数里。原因很简单一个工具后面会有上传任务、下载任务、目录刷新三个动作同时发生如果把QNetworkReply的生命周期散落在各个界面方法里几乎无法管理取消和重试。3.1 界面拆法目录树、任务列表与日志区我习惯把这类工具界面拆成四块左上连接参数区填主机、端口、用户名、密码左侧目录区用QTreeView展示远程目录和文件右侧任务区用QTableWidget显示当前上传下载任务、进度、状态底部是日志区用QPlainTextEdit记录协议层消息。界面代码和网络代码只通过信号槽通信界面完全不碰QNetworkReply对象。这样源码给别人二次开发时改界面不会动网络逻辑换认证方式也不会动界面。任务区的每一行我会在Qt::UserRole里存一个任务对象指针任务对象里记录本地路径、远程路径、类型、当前状态、QNetworkReply指针。这样用户点“取消”按钮时只需要从当前行取出任务对象然后对它调用cancel方法。界面刷新进度时也只绑定任务的信号。这种结构看起来比在按钮里临时new一个reply要绕但到后面加断点续传、加批量队列时你会发现这个绕是省时间的。3.2 FtpClient类的头文件信号、槽与任务映射网络层我通常会封装成一个FtpClient类构造函数收主机端口和认证信息对外暴露listDir、download、upload、cancelAll四个方法。这个类内部持有一个QNetworkAccessManager以及一个从QNetworkReply到本地文件对象的映射。下面是一个可以直接抄的骨架头文件。#ifndef FTPCLIENT_H #define FTPCLIENT_H #include QObject #include QNetworkAccessManager #include QNetworkReply #include QUrl #include QHash class FtpClient : public QObject { Q_OBJECT public: // host: FTP服务器地址或IP // port: 默认21如果服务器用了非标端口就传实际值 // user/password: 登录凭证不支持匿名情况下必填 FtpClient(const QString host, quint16 port, const QString user, const QString password, QObject *parent nullptr); void listDir(const QString remotePath); void download(const QString remoteFile, const QString localFile); void upload(const QString localFile, const QString remoteFile); void cancelAll(); signals: // 目录列表返回后一次性抛出items里是文件名和大小 void listFinished(const QString path, const QListQPairQString, qint64 items); void downloadProgress(const QString remoteFile, qint64 received, qint64 total); void uploadProgress(const QString remoteFile, qint64 sent, qint64 total); void operationFinished(const QString remoteFile, int type, bool success); private: QUrl buildUrl(const QString remotePath); QNetworkAccessManager m_manager; QHashQNetworkReply*, QFile* m_fileMap; // 下载中的reply对应本地文件 QString m_host; quint16 m_port; QString m_user; QString m_password; }; #endif // FTPCLIENT_H头文件的逻辑说明都在注释里这里补三点容易被忽略的参数约束。端口参数要单独存不要拼到URL后面再解析因为后面断点续传时要反复拼URL存原始值最省事。密码里如果带有、/、#这类特殊字符不要手写字符串拼接URL要用QUrl的setPassword方法它会自动做百分号编码。m_fileMap这个映射很关键它保证在多个下载任务并发时每个reply的数据只落到自己的文件里不会串数据。3.3 登录与URL构造认证信息放进URL的注意点FTP的登录在QNetworkAccessManager里不单独处理每次请求的URL里带认证信息后端会在建立控制连接时自动发USER和PASS。所以buildUrl是FtpClient里最基础也最容易出错的方法。常见错误是把路径直接拼在主机后面比如ftp://user:passhost//root/ftp三个斜杠会让路径解析出问题。正确做法是用QUrl的setPath把整个远程路径交给它处理。QUrl FtpClient::buildUrl(const QString remotePath) { QUrl url; url.setScheme(ftp); url.setHost(m_host); url.setPort(m_port); url.setUserName(m_user); url.setPassword(m_password); // remotePath以/开头表示服务器绝对路径这是FTP服务的惯例 url.setPath(remotePath.isEmpty() ? / : remotePath); return url; }这里有几个细节值得单独说。setUserName和setPassword会自动做百分号编码所以密码里有特殊字符时安全编码后的URL显示会比较长但不要为了好看手动去改。setPath传绝对路径即可不要在前面再加斜杠QUrl会处理路径分隔符。如果你访问的是服务器默认目录下的文件remotePath传相对路径也行但我会统一用绝对路径避免登录后默认目录不确定导致文件写错位置。登录失败的表现在后面排查章节再展开这里只需要理解认证是随请求走的。4. 上传下载核心实现从LIST到STOR的落地代码骨架搭好之后核心就是四个功能目录列表、下载、上传、取消。这一章给的是可以直接编译进工程的实现参数细节放在每段代码后面说。FTP的目录列表用的是LIST命令下载对应RETR命令上传对应STOR命令QNetworkAccessManager把这些命令封装成了普通请求所以代码读起来不像FTP协议调试器更像HTTP客户端。4.1 目录列表一份LIST响应能有多少种格式QNetworkAccessManager触发LIST命令有一个关键点当访问的URL路径以斜杠结尾或者显式设置了FtpListAttribute属性时get请求会发送LIST命令否则发送RETR下载。很多人在这踩坑明明是列目录结果把整个目录当文件下载了。下面这段实现同时设置了属性保证行为可预测。void FtpClient::listDir(const QString remotePath) { QNetworkRequest request(buildUrl(remotePath)); // 强制该请求发送LIST命令而不是RETR下载 request.setAttribute(QNetworkRequest::FtpListAttribute, true); QNetworkReply *reply m_manager.get(request); connect(reply, QNetworkReply::finished, this, [this, reply, remotePath]() { QByteArray data reply-readAll(); QListQPairQString, qint64 items; QStringList lines QString::fromUtf8(data).split(\n); for (const QString line : lines) { // Unix风格LIST输出权限 链接数 属主 属组 大小 日期 文件名 QStringList parts line.split(QRegularExpression(\\s)); if (parts.size() 9 parts.at(0).startsWith(-)) { bool ok false; qint64 size parts.at(4).toLongLong(ok); // 文件名可能带空格取第9列之后的所有字段 QString name parts.mid(8).join( ); if (ok !name.isEmpty()) items.append(qMakePair(name, size)); } } emit listFinished(remotePath, items); reply-deleteLater(); }); }LIST响应没有统一标准Unix风格、Windows风格、IIS风格都存在。上面这段只解析Unix风格是FTP服务端最常见的输出但对Windows服务器的返回值会解析失败。常见做法是先用reply-url().path()判断服务器类型或者干脆只取每行最后一个字段当文件名大小字段另做容错。这里的parts.at(4)在Unix风格里是字节数如果解析失败就跳过这条记录保证界面不会因为一条异常行崩溃。QRegularExpression(\s)按连续空白切分比按单个空格split更稳因为服务器可能用制表符对齐列。4.2 下载落地QNetworkReply按readyRead写文件下载的核心逻辑是不要把整个文件读进内存再写盘而是利用reply的readyRead信号收到一块数据就往本地文件里写一块。这样下载2GB文件时内存占用始终维持在一个小范围内。另一个关键点是本地文件的打开时机必须在请求之前否则reply开始返回数据时文件还没创建数据就丢了。void FtpClient::download(const QString remoteFile, const QString localFile) { QFile *file new QFile(localFile); // 下载必须用WriteOnly不能用Append除非是断点续传场景 if (!file-open(QIODevice::WriteOnly | QIODevice::Truncate)) { emit operationFinished(remoteFile, TypeDownload, false); delete file; return; } QNetworkReply *reply m_manager.get(QNetworkRequest(buildUrl(remoteFile))); m_fileMap.insert(reply, file); connect(reply, QNetworkReply::readyRead, this, [this, reply]() { // m_fileMap保证了多个并发下载时文件与reply一一对应 QFile *f m_fileMap.value(reply, nullptr); if (f) f-write(reply-readAll()); }); connect(reply, QNetworkReply::downloadProgress, this, [this, remoteFile](qint64 received, qint64 total) { emit downloadProgress(remoteFile, received, total); }); connect(reply, QNetworkReply::finished, this, [this, reply, remoteFile]() { QFile *f m_fileMap.take(reply); if (f) { f-close(); if (reply-error() ! QNetworkReply::NoError) f-remove(); // 下载失败就删掉半截文件避免下次被当成完整文件 delete f; } emit operationFinished(remoteFile, TypeDownload, reply-error() QNetworkReply::NoError); reply-deleteLater(); }); }写本地文件用WriteOnly加Truncate会覆盖同名旧文件。如果是断点续传这里要改成Append模式并配合偏移后面第六章讲。readyRead里用m_fileMap.value(reply, nullptr)而不是直接在lambda里捕获file裸指针是因为finished和readyRead的触发时机不固定如果任务被cancel导致reply先被销毁裸指针就成了悬垂指针。这个踩坑点我在后面避坑章还会展开。参数上remoteFile是服务器上的绝对路径localFile是本地保存路径调用前需要确保本地目录已经存在否则open失败会走错误分支。4.3 上传文件直接传QFile给put并看进度上传比下载简单因为QNetworkAccessManager的put方法接受一个QIODevice指针内部会分块读取上传不需要自己处理数据切分。容易翻车的做法是先把整个文件读进QByteArray再包成QBuffer大文件一次吃掉几GB内存。正确做法是把打开的QFile直接传给put让管理器逐步读取。void FtpClient::upload(const QString localFile, const QString remoteFile) { QFile *file new QFile(localFile); // 上传必须ReadOnlyput内部负责数据读取和进度上报 if (!file-open(QIODevice::ReadOnly)) { emit operationFinished(remoteFile, TypeUpload, false); delete file; return; } QNetworkRequest request(buildUrl(remoteFile)); // put内部会接管file对象因此这里不要delete file QNetworkReply *reply m_manager.put(request, file); connect(reply, QNetworkReply::uploadProgress, this, [this, remoteFile](qint64 sent, qint64 total) { emit uploadProgress(remoteFile, sent, total); }); connect(reply, QNetworkReply::finished, this, [this, reply, remoteFile]() { emit operationFinished(remoteFile, TypeUpload, reply-error() QNetworkReply::NoError); reply-deleteLater(); }); }put接管QFile所有权这一点要注意reply完成后它自己会delete文件不要再在自己写的代码里delete。uploadProgress的total参数在很多FTP服务端上可能是-1因为数据连接建立前总大小未知界面显示进度条时要做总大小未知的容错比如显示已上传字节数而不是百分比。另外put默认走STOR命令如果服务器对同名文件覆盖有特殊策略比如需要先删除再上传那就要在put之前先发一个删除请求这个在源码改造时往往容易被忽略表现为第二次上传同一文件失败。4.4 取消与超时abort之后还要收拾什么取消操作最容易写漏。用户点击取消直觉上是调用reply-abort()但abort之后会触发finished信号如果finished分支里还在写文件、还在发信号就会出现“取消成功但界面还报错”的怪现象。同时abort会让reply的error变成OperationCanceledError不能用NoError去判断成功否则取消和失败被混在一起。下面是一个取消实现和它配套的超时保护。void FtpClient::cancelAll() { // 枚举m_manager下所有活动reply逐个abort const QListQNetworkReply* replies m_manager.findChildrenQNetworkReply*(); for (QNetworkReply *reply : replies) { reply-abort(); } }超时保护的做法是用QTimer单发超时后主动abort避免服务器无响应时任务栏一直转圈。服务端无响应是FTP工具里很常见的情况不处理的话会让工具看起来像卡死。超时时间我一般设15秒控制连接建立和目录列表都比较快超过15秒通常是网络或服务端出问题了。代码里把timer的父对象设为replyreply销毁时timer自动清理不必手动delete。abort之后检查reply-error()是否是OperationCanceledError在finished槽里区分取消和失败界面才能显示正确的状态。5. FTP工具避坑指南QFtp被移出、中文乱码与防火墙翻车这一章是实际使用中高频踩坑记录的集合。每一条都有现象、原因、解决办法三部分。这些坑不是理论推导出来的是翻车之后一步步查出来和拆出来的直接照着排查能省大把时间。5.1 编译报错老源码第一眼先查QFtp现象工程文件里#include QFtp或者.pro里写了QT network编译时报QFtp头文件找不到或者在链接阶段报一堆未定义符号。原因QFtp在Qt5之后就不在官方包分发目录里了.pro即使声明了net work也拿不到这个类。解决办法有两条路径。一是把老工程里的QFtp调用全部替换成QNetworkAccessManager适合源码体量不大、只有上传下载几个功能的情况。二是找到Qt4兼容模块的源码把ftp模块的cpp和h文件直接拷进工程一起编译这条路适合已经写了很多基于QFtp的业务代码时用。我的建议是你只要想做二次开发就选替换路线因为QFtp的信号槽机制在新版Qt里迟早还会出兼容问题晚改不如早改。5.2 中文文件名乱码协议编码问题的血泪经验现象上传一个叫“测试报告.bin”的文件服务器上显示成乱码或者从服务器下载一个中文名文件本地保存下来的文件名是问号。原因FTP协议没有统一规定控制连接和路径使用哪种字符编码服务端用UTF-8客户端用系统本地编码一个字节一个字节错位自然乱码。解决办法分两头处理。发送侧构造URL时不要直接往路径里放中文用QUrl::toPercentEncoding把中文转成百分号编码让服务器按自己的编码解码。接收侧目录列表拿到的字节流不要直接用fromUtf8先尝试UTF-8解码解码结果里出现无法映射的字符就换GBK再试一次。这个启发式方法不能100%覆盖所有服务器但解决了绝大多数内网场景。5.3 目录列表卡住被动模式与防火墙的关系现象能登录公共目录能看到但某些目录打开后一直转圈最后超时失败。原因FTP的数据连接需要额外协商主动模式是服务器主动连回客户端随机端口被动模式是客户端去连服务器的数据端口。内网客户端往往有防火墙而服务端没配好被动端口范围。QNetworkAccessManager在Qt5的FTP后端默认走主动模式还是被动模式取决于实现但无论哪一种都有可能因为防火墙丢包而卡死。解决办法是优先确认服务端的被动端口范围是否已放开以及客户端所在网络是否允许发起非标准端口连接。如果只是内网工具两端都开放对应端口就能解决问题。排查时看日志如果卡在list阶段先telnet一下服务端的数据端口范围确认端口通不通。5.4 大文件上传内存暴涨不要把文件读进QByteArray现象上传1GB文件内存以肉眼可见速度上涨最后到了2G多崩溃。原因代码里用QFile::readAll()读成大字节数组再丢给上传接口或者更常见的是从网上抄了一段把文件转成QByteArray再包QBuffer的代码。解决办法就是把代码改成第四章上传那种写法直接传打开的QFile给put让网络层内部分块读取。这里有个判断口诀凡是代码里出现“先读完再发”的大文件必爆内存。FTP上传要做到内存占用与文件大小无关才算是能用的工具。改完之后再看上传大文件内存曲线应该是一条平稳直线。5.5 取消下载闪退QNetworkReply悬垂指针的玄学现象用户点下载后立刻点取消或者下载完成瞬间关闭窗口程序偶尔闪退不是必现但一出现就在指针相关代码里。原因lambda里捕获了QNetworkReply裸指针reply在abort或finished后已经销毁lambda再访问它就成了悬垂指针。解决办法是两条。一是在lambda里改用QPointer包裹的reply对象QPointer在对象销毁后自动变null访问前先判空。二是在finished槽里先取走m_fileMap里的文件对象再deleteLater避免在销毁流程里访问已释放对象。这类问题凭静态看代码很难发现跑多线程加取消操作就暴露。源码里如果所有connect都是裸指针捕获基本可以断定没做过取消场景的完整测试。5.6 退出程序卡死线程与reply的清理顺序现象下载大文件过程中直接关窗口程序没有立刻退出任务管理器里进程还在CPU却不高。原因reply还在活动文件句柄没关QNetworkAccessManager析构时会在销毁活动请求时等待或者手动创建的线程没退出。解决办法是在主窗口closeEvent里先cancelAll把所有reply abort掉再等待finished信号处理完成最后关掉文件对象。如果代码里单独开了网络线程则需要请求线程中断并join。一个稳的退出顺序是abort所有请求等所有finished发完关闭m_fileMap里余留的文件再退出事件循环。顺序不能反反了就是偶现假死。6. 断点续传与自测把工具变成无人值守也可靠的工具前面四章做出的是一个能用的FTP工具但这章讲的是让它从“能用”变成“可靠”的关键技巧。可靠的定义是断网重连后能继续传、传完能确认文件没问题、改动后能快速回归验证。6.1 用Range头触发REST实现断点续传断点续传的原理是FTP协议里的REST命令它告诉服务端跳到文件的第N个字节继续传。QNetworkAccessManager的FTP后端会把请求里的Range头转成对应的REST偏移命令。实现时下载前先看本地文件已存在的大小然后带着偏移发起请求。代码如下。void FtpClient::downloadResume(const QString remoteFile, const QString localFile) { QFileInfo info(localFile); qint64 existSize info.exists() ? info.size() : 0; // 本地已下载字节数 QFile *file new QFile(localFile); // 续传必须用Append且seek到末尾不能Truncate if (!file-open(QIODevice::WriteOnly | QIODevice::Append)) { emit operationFinished(remoteFile, TypeDownload, false); delete file; return; } file-seek(existSize); QNetworkRequest request(buildUrl(remoteFile)); if (existSize 0) { // Range头格式是 bytes起始字节- request.setRawHeader(Range, QString(bytes%1-).arg(existSize).toLatin1()); } QNetworkReply *reply m_manager.get(request); m_fileMap.insert(reply, file); connect(reply, QNetworkReply::finished, this, [this, reply, remoteFile]() { QFile *f m_fileMap.take(reply); if (f) { f-close(); if (reply-error() ! QNetworkReply::NoError) f-remove(); delete f; } emit operationFinished(remoteFile, TypeDownload, reply-error() QNetworkReply::NoError); reply-deleteLater(); }); }续传的边界条件比普通下载多一个如果服务端不支持REST命令返回错误时本地文件会被我上面的代码删除这其实是保护行为清掉半截文件避免它被当成完整结果。另一个要注意的是续传和覆盖下载是两种模式界面上要分开按钮不要自动续传。因为有时候用户就是要重新下载整个文件自动续传会把旧文件内容留到新文件里造成两份文件拼接的隐患。6.2 上传后的校验思路FTP协议没有标准的文件MD5校验命令有的服务端支持MFMT或HASH扩展但兼容性参差。可靠的办法是上传完成后把文件下载回来做比对但这样耗时翻倍。折中方案是下载目录列表比对服务器返回的文件大小与本地文件大小。目录列表里的数字是服务端算好的字节数和本地大小相等基本能说明传完整了。如果对完整性要求苛刻再考虑两次下载采样校验取文件头、中间、末尾各几KB做MD5对比不用整盘下载就能发现绝大多数传输损坏问题。内网环境下这个方案性价比很高。6.3 一条自测流程让改动可回归每次改完FTP源码我会用一个固定脚本验收上传、下载、比对三步走。脚本逻辑很简单但能覆盖协议层、认证层、读写层三个层面的回归。#!/usr/bin/env bash set -euo pipefail # 前提本机21端口已有FTP服务且根目录可写 src/tmp/ftp_test_$(date %s).bin dst/tmp/ftp_test_download.bin # 生成1MB随机文件避免用全0文件掩盖数据问题 head -c 1048576 /dev/urandom $src md5_before$(md5sum $src | cut -d -f1) # 假设源码编译出来的命令行程序叫 ftp_tool子命令自行替换 ./ftp_tool upload $src /test.bin --host 127.0.0.1 --port 21 ./ftp_tool download /test.bin $dst --host 127.0.0.1 --port 21 md5_after$(md5sum $dst | cut -d -f1) if [ $md5_before $md5_after ]; then echo PASS else echo FAIL exit 1 fi这个脚本的用处是把早期上线时遇到过的中文乱码问题挡住。我遇到过一次改完编码逻辑界面正常了但下载回的文件和源文件MD5对不上就是脚本在跑回归时发现的。之后我养成一个习惯任何一次FTP网络层改动不管多小都先跑一遍这个脚本再合入版本。FTP工具源码改动频繁的是编码、认证、任务调度这几块都直接关系传输结果没有回归验证的改动就是在赌。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Loop Engineering:多智能体协作的工程范式

Loop Engineering:多智能体协作的工程范式

1. 什么是Loop Engineering:不是“循环”,而是智能体协作的底层操作系统 Loop Engineering这个词,最近在技术圈里突然密集出现,但很多人第一反应是——这不就是个for循环?或者跟控制理论里的反馈回路有关?其…

📅 2026/10/15 12:36:11
五种图像生成范式本质解析:扩散、自回归、视觉token化、概念具象化与端侧残差

五种图像生成范式本质解析:扩散、自回归、视觉token化、概念具象化与端侧残差

1. 这不是“又一个文生图模型”——而是五种截然不同的图像生成范式正在交汇你点开这篇内容,大概率是因为在ComfyUI节点里看到FLUX.2、Seedream、Z-image、Qwen-Image、GLM-Image这五个名字并列出现,却完全搞不清它们到底在干什么。有人在蓝奏云下载Z-im…

📅 2026/10/15 12:36:11
免费开源AI编码代理:GUI操控与MCP集成,单文件运行

免费开源AI编码代理:GUI操控与MCP集成,单文件运行

我做了个免费 AI 编码代理:支持操控 GUI 和 MCP,单文件运行。这东西不是什么大厂产品,是我自己利用业余时间写的一个开源小工具,核心就三件事:让大模型不光能写代码,还能直接操作你屏幕上的图形界面程序&am…

📅 2026/10/15 12:36:11
MORE NEWS

更多资讯

📰

webpack打包纯静态商城站:多页面资源处理与工程化实践

1. 从散落的静态文件到可交付产物:这次打包到底要解决什么手里有一套品优购的静态商城页面,纯HTML、CSS加一点图片和字体图标,没有框架、没有模块化、没有构建工具。这种项目在本地双击打开index.html就能跑,看起来岁月静好&#…

📰

思科华为组播配置与排错实战:从RPF原理到IGMP Snooping部署

1. 从一个真实运维场景说起:为什么组播总在关键时刻掉链子很多做网络运维的朋友都有过类似的经历:公司要搞一场全员线上直播培训,或者园区要部署一套视频监控系统,单播方案跑几十路就已经把核心链路带宽吃满了,于是决定…

📰

Qt+CMake三大配置范式:从Demo到企业级项目的演进路径

1. 为什么Qt项目里CMakeLists.txt总让人“改得心虚”?你有没有过这种经历:刚用Qt Creator新建一个Qt Widgets Application,点开CMakeLists.txt一看,满屏的find_package(Qt6 REQUIRED COMPONENTS Widgets)、target_link_libraries(…

📰

吃透六自由度机器人D-H法:建模、正逆解与避坑全攻略

简介:六自由度机器人D-H法学习资料共3个文件,压缩包仅2.27MB,包含2个MATLAB程序(m文件)和1份Word说明文档。资料面向机器人学初学者、高校相关课程学生以及需要完成运动学作业的工程师,系统讲解D-H参数法原…

📰

Python调用Simulink编译DLL:零MATLAB依赖的轻量部署方案

简介:本资源是一套面向Python开发者与嵌入式仿真工程师的Simulink模型加速实践方案,聚焦于将Simulink模型编译为DLL并在Python中高效调用,显著降低MIL(模型在环)测试对MATLAB/Simulink许可证和运行时环境的依赖。资源以…

📰

Oransim路线图前瞻:从v0.2到因果基础模型,闭环AI媒体投放与百万级智能体集群的完整规划

【免费下载链接】oransim Causal Digital Twin for Marketing at Scale Predict any marketing decision before you spend a dollar. 项目地址: https://gitcode.com/gh_mirrors/or/oransim 点击查看 免费下载 Oransim 是一个开源的营销因果数字孪生框架&#xf…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬