开源libcurl 实战: FTP 高级下载功能实现:断点续传、分片下载与批量处理
·
之前用Qt开发时用Qt4 QFtp,在使用中发现诸多不便,比如很难处理超时,无法进行偏移下载、分片下载等等。因此萌生了用libcurl替代的想法,以下是主要的技术路线和实现细节供参考。
Qt4 QFtp与libcurl的对比
Qt4 QFtp的特点
Qt4提供的QFtp模块是Qt框架原生的FTP实现,具有以下特点:
优点:
- 与Qt框架深度集成,使用信号槽机制处理异步操作,符合Qt编程习惯
- 无需额外依赖,作为Qt的一部分直接使用
- 接口封装层次高,上手简单,适合快速开发
- 支持FTP协议的基本操作(上传、下载、目录浏览等)
缺点:
- 功能相对简单,高级特性支持不足(如断点续传、多线程下载等)
- 性能表现一般,在大文件传输场景下效率不高
- 错误处理机制不够完善,调试难度较大
- Qt5及以上版本已移除QFtp模块,改用QNetworkAccessManager,兼容性受限
- 对FTP协议的一些扩展支持有限
libcurl的特点
libcurl是一个功能强大的开源网络传输库,支持包括FTP在内的多种协议:
优点:
- 支持丰富的协议(FTP、HTTP、HTTPS等),功能全面
- 提供完善的FTP特性支持(断点续传、主动/被动模式、SSL加密等)
- 性能优异,适合大文件传输和高并发场景
- 活跃的社区支持和持续的更新维护
- 跨平台性好,在各种操作系统上表现一致
- 详细的调试信息和完善的错误处理机制
缺点:
- 基于C语言接口,在C++项目中需要自行封装
- 与Qt等框架的集成需要额外工作
- 学习曲线相对较陡,需要理解更多底层细节
因此,对于简单的FTP需求且使用Qt4开发,QFtp足以胜任;但对于需要高级特性、更好性能或跨框架的项目,libcurl是更优选择。
一、整体设计
本FTP客户端采用分层设计思想,将功能划分为三个核心层次:
- 底层通信层:基于libcurl库实现FTP协议的底层通信,封装CURL的初始化、配置、执行和清理流程
- 业务逻辑层:实现FTP核心功能(登录、目录浏览、上传下载等),处理协议细节和错误恢复
- UI交互层:基于Qt框架实现用户界面,通过信号槽机制与业务逻辑层交互
核心类关系如下:
Widget(UI层) <--信号槽--> FtpThread(业务逻辑层) <--调用--> libcurl(底层通信层)
二、核心功能实现路线
1. FtpThread类设计
class FtpThread : public QObject {
Q_OBJECT
public:
explicit FtpThread(QObject *parent = nullptr);
explicit FtpThread(const FtpInfo& info,QObject *parent = nullptr);
~FtpThread();
...
int login(const FtpInfo& info);
int dirlist(const QString& directory);
int startMultiDownload(const QStringList &remotepath,const QStringList& localPath, bool resume = false,size_t offset=0,size_t len=0 );
int startDownload(const QString& remotePath, const QString& localPath, bool resume = false,size_t offset=0,size_t len=0 );
int stopDownload();
...
signals:
void dirListReady(QVector<FtpEntry> entries);
void downloadProgress(int percent,int bw);
void downloadFinished(QString localFilePath);
void downloadError(QString error);
private:
CURL* curl_init(CommandCode code);
void curl_exit(CURL*);
static size_t listCallback(void* ptr, size_t size, size_t nmemb, std::string* data);
static size_t downloadCallback(void* ptr, size_t size, size_t nmemb, std::ofstream* stream);
static int downloadProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow);
int performDownload(const QString& remoteUrl, const QString& localPath, bool resume,size_t offset=0,size_t len=0);
};
2. 目录列表获取与解析
目录浏览功能需要处理FTP服务器返回的列表数据,并解析为结构化信息:
CURL* curl = curl_init();
curl_easy_setopt(curl, CURLOPT_URL, fullUrl.toUtf8().constData());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, listCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &listData);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
3. 文件下载功能实现
下载功能是FTP客户端的核心,本实现支持断点续传、指定范围下载和文件切割下载等高级特性。
3.1 基础下载
curl_easy_setopt(curl, CURLOPT_URL, encodedRemoteUrl.toUtf8().constData());
curl_easy_setopt(curl, CURLOPT_USERNAME, m_serverInfo.userName.toUtf8().constData());
curl_easy_setopt(curl, CURLOPT_PASSWORD, m_serverInfo.passWord.toUtf8().constData());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, downloadCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &outFile);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgressCallback);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
3.2 断点续传、范围下载、分片下载
通过CURLOPT_RESUME_FROM_LARGE和CURLOPT_RANGE实现断点续传和指定范围下载:
在特殊使用场景下,要求对文件按照特定大小进行切割下载。也是基于CURLOPT_RANGE实现
std::string FtpThread::setRange(size_t start, size_t len) {
if (len == 0) {
return QString("%1-").arg(start).toStdString(); // 从start到文件末尾
} else {
return QString("%1-%2").arg(start).arg(start + len - 1).toStdString();
}
}
// 设置断点续传
if (resume) {
qDebug()<<"resume download:"<<fileSize<<len;
curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, fileSize);
}
//Range下载、分片下载
if (range){
std::string range=setRange(offset,len);
curl_easy_setopt(curl, CURLOPT_RANGE, range.c_str());
}
3.3 多线程与批量下载
为避免UI阻塞,下载操作在独立线程中执行:
int FtpThread::startMultiDownload(const QStringList &remotePaths, const QStringList& localPaths,
bool resume, size_t offset, size_t len) {
if (m_downloadThread.joinable()) {
m_downloadThread.join();
}
m_isDownloading = true;
m_downloadThread = std::thread([this, remotePaths, localPaths, resume, offset, len]() {
for(int i = 0 ; i < remotePaths.size(); i++){
int result = performDownload(url, localPaths.at(i), resume, offset, len);
if (result == 0 && m_isDownloading) {
emit downloadFinished(remotePaths.at(i));
} else {
break;
}
}
m_isDownloading = false;
});
return 0;
}
4. 回调函数监控进度和速率
通过进度回调函数实时跟踪传输状态,并计算传输速率:
int FtpThread::downloadProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t , curl_off_t ) {
auto* worker = static_cast<FtpThread*>(clientp);
// 计算并发送进度(每秒更新一次)
static curl_off_t lastBytes = 0;
static auto lastUpdate = std::chrono::steady_clock::now() - std::chrono::seconds(10);
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastUpdate);
if (duration.count() >= 1000 ) {
lastUpdate = now;
int percent = dltotal > 0 ? static_cast<int>((dlnow * 100) / dltotal) : 0;
// 计算传输速率(MB/s)
float speed = 1000.0 * (static_cast<long long>(dlnow - lastBytes)) /
duration.count() / 1000 / 1000;
lastBytes = dlnow;
emit worker->downloadProgress(percent, speed);
}
return 0;
}
三、一些注意事项
1) Windows系统下的中文编码问题通过编码转换解决:
2) URL中的空格处理:
QString encodedRemoteUrl = remoteUrl;
encodedRemoteUrl.replace(" ", "%20"); // 空格替换为URL编码
3)通过原子变量和互斥锁确保线程安全
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)