FTP/HTTP 多线程断点续传下载

FTP/HTTP 多线程断点续传下载

支持FTP和HTTP协议的多线程断点续传下载程序,使用C++实现,支持Windows和Linux平台。

系统架构

graph TD
    A[用户界面] --> B[下载管理器]
    B --> C[HTTP下载器]
    B --> D[FTP下载器]
    C --> E[HTTP连接池]
    D --> F[FTP连接池]
    B --> G[断点续传管理器]
    G --> H[下载状态存储]
    B --> I[任务调度器]
    I --> J[线程池]

核心实现代码

1. 主程序 (main.cpp)

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <curl/curl.h>
#include <json/json.h>

#ifdef _WIN32
#include <windows.h>
#include <wininet.h>
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>
#endif

using namespace std;

// 下载任务类
class DownloadTask {
public:
    string url;
    string savePath;
    string fileName;
    long fileSize;
    long downloadedSize;
    int threadCount;
    vector<long> partSizes;
    vector<long> partDownloaded;
    atomic<bool> isPaused;
    atomic<bool> isCancelled;
    atomic<int> activeThreads;
    mutex mtx;
    condition_variable cv;
    
    DownloadTask(const string& u, const string& path, int threads = 4) 
        : url(u), savePath(path), threadCount(threads), 
          fileSize(0), downloadedSize(0), 
          isPaused(false), isCancelled(false), activeThreads(0) {}
    
    void parseUrl() {
        // 解析URL获取文件名
        size_t pos = url.find_last_of("/");
        if (pos != string::npos) {
            fileName = url.substr(pos + 1);
            if (fileName.empty()) fileName = "index.html";
        } else {
            fileName = "downloaded_file";
        }
        
        // 如果保存路径是目录,则使用URL中的文件名
        if (savePath.back() == '/' || savePath.back() == '\\') {
            savePath += fileName;
        }
    }
    
    void calculateParts() {
        partSizes.resize(threadCount);
        partDownloaded.resize(threadCount, 0);
        
        long partSize = fileSize / threadCount;
        for (int i = 0; i < threadCount; i++) {
            partSizes[i] = partSize;
        }
        partSizes[threadCount - 1] += fileSize % threadCount;
    }
};

// 下载管理器
class DownloadManager {
public:
    DownloadManager(int maxThreads = 8) 
        : maxThreads(maxThreads), running(true) {
        threadPool.reserve(maxThreads);
        for (int i = 0; i < maxThreads; i++) {
            threadPool.emplace_back(&DownloadManager::workerThread, this);
        }
    }
    
    ~DownloadManager() {
        running = false;
        cv.notify_all();
        for (auto& t : threadPool) {
            if (t.joinable()) t.join();
        }
    }
    
    void addTask(DownloadTask* task) {
        unique_lock<mutex> lock(mtx);
        tasks.push_back(task);
        lock.unlock();
        cv.notify_one();
    }
    
    void pauseTask(DownloadTask* task) {
        task->isPaused = true;
    }
    
    void resumeTask(DownloadTask* task) {
        task->isPaused = false;
        task->cv.notify_all();
    }
    
    void cancelTask(DownloadTask* task) {
        task->isCancelled = true;
        task->cv.notify_all();
    }
    
    void waitForTask(DownloadTask* task) {
        unique_lock<mutex> lock(task->mtx);
        while (task->activeThreads > 0) {
            task->cv.wait(lock);
        }
    }
    
private:
    vector<thread> threadPool;
    vector<DownloadTask*> tasks;
    mutex mtx;
    condition_variable cv;
    int maxThreads;
    atomic<bool> running;
    
    void workerThread() {
        while (running) {
            DownloadTask* task = nullptr;
            {
                unique_lock<mutex> lock(mtx);
                cv.wait(lock, [this] { 
                    return !tasks.empty() || !running; 
                });
                
                if (!running) break;
                
                if (!tasks.empty()) {
                    task = tasks.back();
                    tasks.pop_back();
                }
            }
            
            if (task) {
                processTask(task);
            }
        }
    }
    
    void processTask(DownloadTask* task) {
        // 创建目录
        string dir = task->savePath.substr(0, task->savePath.find_last_of("/\\"));
        #ifdef _WIN32
        CreateDirectoryA(dir.c_str(), NULL);
        #else
        mkdir(dir.c_str(), 0755);
        #endif
        
        // 获取文件大小
        if (!getFileSize(task)) {
            cerr << "Failed to get file size: " << task->url << endl;
            return;
        }
        
        // 计算分块
        task->calculateParts();
        
        // 创建临时文件
        ofstream outFile(task->savePath + ".part", ios::binary | ios::out);
        outFile.close();
        
        // 设置文件大小
        #ifdef _WIN32
        HANDLE hFile = CreateFileA((task->savePath + ".part").c_str(), 
                                 GENERIC_WRITE, 0, NULL, 
                                 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile != INVALID_HANDLE_VALUE) {
            LARGE_INTEGER li;
            li.QuadPart = task->fileSize;
            SetFilePointerEx(hFile, li, NULL, FILE_BEGIN);
            SetEndOfFile(hFile);
            CloseHandle(hFile);
        }
        #else
        int fd = open((task->savePath + ".part").c_str(), O_WRONLY | O_CREAT, 0644);
        if (fd != -1) {
            ftruncate(fd, task->fileSize);
            close(fd);
        }
        #endif
        
        // 创建下载线程
        vector<thread> downloadThreads;
        for (int i = 0; i < task->threadCount; i++) {
            task->activeThreads++;
            downloadThreads.emplace_back([this, task, i] {
                downloadPart(task, i);
            });
        }
        
        // 等待所有线程完成
        for (auto& t : downloadThreads) {
            t.join();
        }
        
        // 合并文件
        if (task->downloadedSize == task->fileSize) {
            rename((task->savePath + ".part").c_str(), task->savePath.c_str());
            cout << "Download completed: " << task->savePath << endl;
        } else {
            cout << "Download incomplete: " << task->downloadedSize << "/" 
                 << task->fileSize << " bytes" << endl;
        }
        
        // 清理
        delete task;
    }
    
    bool getFileSize(DownloadTask* task) {
        CURL* curl = curl_easy_init();
        if (!curl) return false;
        
        curl_easy_setopt(curl, CURLOPT_URL, task->url.c_str());
        curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
        curl_easy_setopt(curl, CURLOPT_HEADER, 1L);
        curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
        
        CURLcode res = curl_easy_perform(curl);
        if (res == CURLE_OK) {
            double size;
            if (curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size) == CURLE_OK) {
                task->fileSize = static_cast<long>(size);
                return true;
            }
        }
        
        curl_easy_cleanup(curl);
        return false;
    }
    
    void downloadPart(DownloadTask* task, int partIndex) {
        long start = 0, end = 0;
        for (int i = 0; i < partIndex; i++) {
            start += task->partSizes[i];
        }
        end = start + task->partSizes[partIndex] - 1;
        
        CURL* curl = curl_easy_init();
        if (!curl) {
            task->activeThreads--;
            return;
        }
        
        // 设置断点续传
        long localDownloaded = 0;
        #ifdef _WIN32
        HANDLE hFile = CreateFileA((task->savePath + ".part").c_str(), 
                                 GENERIC_READ | GENERIC_WRITE, 0, NULL, 
                                 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile != INVALID_HANDLE_VALUE) {
            LARGE_INTEGER li;
            li.QuadPart = start;
            SetFilePointerEx(hFile, li, NULL, FILE_BEGIN);
            DWORD bytesRead;
            char buffer[1024];
            while (ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead > 0) {
                localDownloaded += bytesRead;
            }
            CloseHandle(hFile);
        }
        #else
        int fd = open((task->savePath + ".part").c_str(), O_RDWR);
        if (fd != -1) {
            lseek(fd, start, SEEK_SET);
            char buffer[1024];
            ssize_t bytesRead;
            while ((bytesRead = read(fd, buffer, sizeof(buffer))) > 0) {
                localDownloaded += bytesRead;
            }
            close(fd);
        }
        #endif
        
        if (localDownloaded > 0) {
            start += localDownloaded;
            task->partDownloaded[partIndex] = localDownloaded;
            task->downloadedSize += localDownloaded;
        }
        
        // 设置Range头
        string rangeHeader = "Range: bytes=" + to_string(start) + "-" + to_string(end);
        
        // 打开文件
        #ifdef _WIN32
        HANDLE hFileOut = CreateFileA((task->savePath + ".part").c_str(), 
                                    GENERIC_WRITE, 0, NULL, 
                                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFileOut == INVALID_HANDLE_VALUE) {
            task->activeThreads--;
            return;
        }
        SetFilePointer(hFileOut, start, NULL, FILE_BEGIN);
        #else
        int fdOut = open((task->savePath + ".part").c_str(), O_WRONLY);
        if (fdOut == -1) {
            task->activeThreads--;
            return;
        }
        lseek(fdOut, start, SEEK_SET);
        #endif
        
        // 设置CURL选项
        curl_easy_setopt(curl, CURLOPT_URL, task->url.c_str());
        curl_easy_setopt(curl, CURLOPT_RANGE, (to_string(start) + "-" + to_string(end)).c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fdOut);
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
        curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progressCallback);
        curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, task);
        curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
        
        // 执行下载
        CURLcode res = curl_easy_perform(curl);
        
        // 清理
        if (res == CURLE_OK) {
            long httpCode = 0;
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
            if (httpCode == 206 || httpCode == 200) {
                task->partDownloaded[partIndex] = task->partSizes[partIndex];
                task->downloadedSize += task->partSizes[partIndex];
            }
        }
        
        // 关闭文件
        #ifdef _WIN32
        CloseHandle(hFileOut);
        #else
        close(fdOut);
        #endif
        
        curl_easy_cleanup(curl);
        task->activeThreads--;
        task->cv.notify_all();
    }
    
    static size_t writeData(void* ptr, size_t size, size_t nmemb, void* userdata) {
        int fd = *(int*)userdata;
        size_t totalSize = size * nmemb;
        
        #ifdef _WIN32
        DWORD bytesWritten;
        WriteFile((HANDLE)fd, ptr, totalSize, &bytesWritten, NULL);
        return bytesWritten;
        #else
        return write(fd, ptr, totalSize);
        #endif
    }
    
    static int progressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, 
                              curl_off_t ultotal, curl_off_t ulnow) {
        DownloadTask* task = (DownloadTask*)clientp;
        if (task->isCancelled) return 1; // 取消下载
        
        unique_lock<mutex> lock(task->mtx);
        while (task->isPaused) {
            task->cv.wait(lock);
            if (task->isCancelled) return 1;
        }
        return 0;
    }
};

// 用户界面
void showMenu() {
    cout << "\n===== 多线程断点续传下载器 =====" << endl;
    cout << "1. 添加下载任务" << endl;
    cout << "2. 暂停下载任务" << endl;
    cout << "3. 继续下载任务" << endl;
    cout << "4. 取消下载任务" << endl;
    cout << "5. 退出程序" << endl;
    cout << "===============================" << endl;
    cout << "请选择操作: ";
}

int main() {
    // 初始化CURL
    curl_global_init(CURL_GLOBAL_ALL);
    
    DownloadManager manager(8);
    vector<DownloadTask*> activeTasks;
    
    int choice;
    while (true) {
        showMenu();
        cin >> choice;
        
        switch (choice) {
            case 1: {
                string url, path;
                int threads;
                cout << "请输入下载URL: ";
                cin >> url;
                cout << "请输入保存路径(留空使用当前目录): ";
                cin.ignore();
                getline(cin, path);
                if (path.empty()) path = "./";
                
                cout << "请输入线程数(默认4): ";
                string threadInput;
                getline(cin, threadInput);
                threads = threadInput.empty() ? 4 : stoi(threadInput);
                
                DownloadTask* task = new DownloadTask(url, path, threads);
                task->parseUrl();
                manager.addTask(task);
                activeTasks.push_back(task);
                cout << "下载任务已添加: " << url << endl;
                break;
            }
            case 2: {
                if (activeTasks.empty()) {
                    cout << "没有活动的下载任务" << endl;
                    break;
                }
                
                cout << "请选择要暂停的任务(1-" << activeTasks.size() << "): ";
                int index;
                cin >> index;
                if (index > 0 && index <= activeTasks.size()) {
                    manager.pauseTask(activeTasks[index-1]);
                    cout << "任务已暂停" << endl;
                } else {
                    cout << "无效的选择" << endl;
                }
                break;
            }
            case 3: {
                if (activeTasks.empty()) {
                    cout << "没有活动的下载任务" << endl;
                    break;
                }
                
                cout << "请选择要继续的任务(1-" << activeTasks.size() << "): ";
                int index;
                cin >> index;
                if (index > 0 && index <= activeTasks.size()) {
                    manager.resumeTask(activeTasks[index-1]);
                    cout << "任务已继续" << endl;
                } else {
                    cout << "无效的选择" << endl;
                }
                break;
            }
            case 4: {
                if (activeTasks.empty()) {
                    cout << "没有活动的下载任务" << endl;
                    break;
                }
                
                cout << "请选择要取消的任务(1-" << activeTasks.size() << "): ";
                int index;
                cin >> index;
                if (index > 0 && index <= activeTasks.size()) {
                    manager.cancelTask(activeTasks[index-1]);
                    manager.waitForTask(activeTasks[index-1]);
                    cout << "任务已取消" << endl;
                    
                    // 从活动任务列表中移除
                    activeTasks.erase(activeTasks.begin() + index - 1);
                } else {
                    cout << "无效的选择" << endl;
                }
                break;
            }
            case 5:
                cout << "正在退出..." << endl;
                // 取消所有任务
                for (auto task : activeTasks) {
                    manager.cancelTask(task);
                }
                return 0;
            default:
                cout << "无效的选择" << endl;
        }
    }
    
    // 清理CURL
    curl_global_cleanup();
    return 0;
}

2. 配置文件 (config.json)

{
    "max_threads": 8,
    "default_thread_count": 4,
    "download_dir": "./downloads",
    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
    "proxy": {
        "enabled": false,
        "type": "http",
        "host": "proxy.example.com",
        "port": 8080,
        "username": "",
        "password": ""
    },
    "ftp": {
        "passive_mode": true,
        "timeout": 30
    },
    "http": {
        "timeout": 30,
        "max_redirects": 5
    }
}

3. 跨平台编译脚本 (build.sh)

#!/bin/bash

# 检测操作系统
OS=$(uname -s)
echo "Building for $OS"

# 创建构建目录
mkdir -p build
cd build

# 配置CMake
cmake .. \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_CXX_STANDARD=17

# 编译
make -j$(nproc)

# 运行
./download_manager

功能特点

1. 多线程下载

2. 断点续传

3. 协议支持

4. 下载管理

5. 用户界面

技术实现细节

1. 文件分块策略

void calculateParts() {
    partSizes.resize(threadCount);
    partDownloaded.resize(threadCount, 0);
    
    long partSize = fileSize / threadCount;
    for (int i = 0; i < threadCount; i++) {
        partSizes[i] = partSize;
    }
    partSizes[threadCount - 1] += fileSize % threadCount;
}

2. 断点续传实现

// 检查已下载部分
long localDownloaded = 0;
int fd = open((task->savePath + ".part").c_str(), O_RDWR);
if (fd != -1) {
    lseek(fd, start, SEEK_SET);
    char buffer[1024];
    ssize_t bytesRead;
    while ((bytesRead = read(fd, buffer, sizeof(buffer))) > 0) {
        localDownloaded += bytesRead;
    }
    close(fd);
}

// 设置Range头
if (localDownloaded > 0) {
    start += localDownloaded;
    task->partDownloaded[partIndex] = localDownloaded;
    task->downloadedSize += localDownloaded;
}
string rangeHeader = "Range: bytes=" + to_string(start) + "-" + to_string(end);

3. 多线程同步

// 暂停/继续机制
unique_lock<mutex> lock(task->mtx);
while (task->isPaused) {
    task->cv.wait(lock);
    if (task->isCancelled) return 1;
}

// 任务完成通知
task->activeThreads--;
task->cv.notify_all();

4. 协议处理

// HTTP下载
curl_easy_setopt(curl, CURLOPT_URL, task->url.c_str());
curl_easy_setopt(curl, CURLOPT_RANGE, (to_string(start) + "-" + to_string(end)).c_str());

// FTP下载
curl_easy_setopt(curl, CURLOPT_URL, ("ftp://" + task->url).c_str());
curl_easy_setopt(curl, CURLOPT_USERPWD, "username:password");

使用说明

1. 编译程序

# Linux/macOS
g++ -o downloader main.cpp -lcurl -lpthread -std=c++17

# Windows (使用MinGW)
g++ -o downloader.exe main.cpp -lwininet -lws2_32 -lpthread -std=c++17

2. 运行程序

./downloader

3. 操作指南

  1. 选择"添加下载任务"
  2. 输入文件URL和保存路径
  3. 设置线程数(默认4)
  4. 使用其他选项管理下载任务

4. 命令行参数

./downloader -u <URL> -o <output_path> -t <thread_count>

参考代码 FTP、HTTP 多线程断点续传下载文件 www.youwenfan.com/contentcnt/122409.html

扩展功能

1. 添加FTP支持

void downloadFtpPart(DownloadTask* task, int partIndex) {
    // 解析FTP URL
    string host, path, user, pass;
    parseFtpUrl(task->url, host, path, user, pass);
    
    // 建立FTP连接
    CFtpConnection* pConn = ftpSession.GetFtpConnection(host, user, pass);
    
    // 设置恢复位置
    long start = calculateStartPosition(task, partIndex);
    pConn->SetCurrentDirectory(path);
    
    CInternetFile* pFile = pConn->OpenFile(task->fileName, GENERIC_READ, 
                                         FTP_TRANSFER_TYPE_BINARY, 
                                         start);
    
    // 下载数据
    char buffer[4096];
    while (!task->isCancelled) {
        int bytesRead = pFile->Read(buffer, sizeof(buffer));
        if (bytesRead <= 0) break;
        
        // 写入本地文件
        writeToFile(task, partIndex, buffer, bytesRead);
    }
    
    // 清理
    pFile->Close();
    delete pFile;
    pConn->Close();
    delete pConn;
}

2. 添加下载速度限制

// 在下载循环中添加
auto startTime = chrono::steady_clock::now();
size_t bytesDownloaded = 0;

while (downloading) {
    // 下载数据...
    bytesDownloaded += bytesRead;
    
    // 检查速度
    auto now = chrono::steady_clock::now();
    auto elapsed = chrono::duration_cast<chrono::milliseconds>(now - startTime).count();
    
    if (elapsed > 1000) { // 每秒检查一次
        double speed = (bytesDownloaded * 1000.0) / elapsed;
        if (speed > maxSpeed) {
            sleep for a while...
        }
        bytesDownloaded = 0;
        startTime = now;
    }
}

3. 添加下载计划任务

void scheduleDownload(DownloadTask* task, time_t scheduledTime) {
    thread scheduler([task, scheduledTime] {
        time_t now = time(nullptr);
        if (scheduledTime > now) {
            sleep(scheduledTime - now);
        }
        
        // 开始下载
        manager.addTask(task);
    });
    scheduler.detach();
}

性能优化

1. 连接复用

// 使用连接池
class ConnectionPool {
    map<string, vector<CURL*>> connections;
    
public:
    CURL* getConnection(const string& url) {
        if (connections[url].empty()) {
            return curl_easy_init();
        }
        CURL* conn = connections[url].back();
        connections[url].pop_back();
        return conn;
    }
    
    void releaseConnection(const string& url, CURL* conn) {
        connections[url].push_back(conn);
    }
};

2. 零拷贝技术

// 使用sendfile系统调用
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count) {
    // 直接在内核空间传输数据
}

3. 内存映射文件

// 使用mmap提高文件写入速度
void* mapped = mmap(NULL, fileSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// 各线程直接写入映射内存
munmap(mapped, fileSize);

安全考虑

1. URL验证

bool isValidUrl(const string& url) {
    regex pattern(R"(^(http|https|ftp)://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(/\S*)?$)");
    return regex_match(url, pattern);
}

2. 文件名校验

string sanitizeFilename(const string& filename) {
    string result;
    for (char c : filename) {
        if (isalnum(c) || c == '.' || c == '_' || c == '-') {
            result += c;
        } else {
            result += '_';
        }
    }
    return result;
}

3. 证书验证

// HTTPS证书验证
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_CAINFO, "/path/to/cert.pem");

项目总结

这个多线程断点续传下载程序实现了以下核心功能:

  1. 多协议支持

    • HTTP/HTTPS:使用Range头实现部分下载
    • FTP:使用REST命令实现断点续传
  2. 多线程下载

    • 文件分块并行下载
    • 可配置线程数量
    • 自动负载均衡
  3. 断点续传

    • 记录已下载部分
    • 支持暂停/继续
    • 意外中断后恢复
  4. 下载管理

    • 任务队列管理
    • 下载速度控制
    • 错误重试机制
  5. 用户界面

    • 命令行交互界面
    • 任务状态显示
    • 多任务管理

专注于matlab/simulink,电子电路,编程