C语言实现多线程下载

本文介绍c语言实现多线程下载,使用 curl库

需求分析

对于一个文件的下载,我们有多种情况需要考虑:

1.单线程下载还是多线程下载

2.如果是多线程下载,如何保证下载结果正确

3.能否实现断点续传

4.能否实现多文件下载

设计

1.使用curl库

  • 优点是功能和接口成熟

  • 缺点是不能跨平台

2.使用多线程下载

  • 将目标文件拆分成多份,每个线程下载各自的部分,互不影响
  • 即分片下载

3.注册信号来捕捉程序退出,在退出前保存当前下载进度到文件,每次下载先读取文件,获取上次进度,再下载

  • 配合curl接口,保存每个线程的下载范围
  • 在每次保存进度文件时先清空上一次的文件,避免混乱

4.多文件下载需要fork多进程,本文暂不实现

实现

1.首先需要一个结构体来保存目标文件的一些信息,便于管理

typedef struct fileInfo {
    const char *url;        //  下载文件的URL
    char *fileptr;          //  文件内存映射指针
    int offset;             //  当前下载偏移量
    int end;                //  文件结尾位置
    pthread_t thid;         //  线程句柄
    double download;        //  已下载字节数
    double totaldownload;   //  文件总大小
    FILE *recordfile;       //  下载进度文件指针
} fileInfo;

2.下载前需要获取文件大小,用于分片

double getDownloadFileLength(const char *url) {

    CURL *curl = curl_easy_init();

    printf("url: %s\n", url);
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HEADER, 1);
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1);  // 只获取头部信息

    CURLcode res = curl_easy_perform(curl);
    if (res == CURLE_OK) {
        curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &downloadFileLength);
    } else {
        printf("get downloadFileLength error\n");
        downloadFileLength = -1;
    }

    curl_easy_cleanup(curl);

    return downloadFileLength;
}
  • 主要通过curl库接口实现功能,需要对接口熟悉

3.分片下载

int download(const char *url, const char *filename) {

    long fileLength = getDownloadFileLength(url);
    printf("downloadFileLength: %ld\n", fileLength);

    int fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        return -1;
    }
    // 创建一个相同大小的空文件
    if (-1 == lseek(fd, fileLength-1, SEEK_SET)) {
        perror("lseek");
        close(fd);
        return -1;
    }
    if (1 != write(fd, "", 1)) {
        perror("write");
        close(fd);
        return -1;
    }
    // 映射该文件到用户态
    char *fileptr = (char *)mmap(NULL, fileLength, PROT_READ| PROT_WRITE, MAP_SHARED, fd, 0);
    if (fileptr == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return -1;
    }

    FILE *fp = fopen("a.txt", "r");
    // 分片下载
    int i = 0;
    long partsize = fileLength / THREAD_COUNT;
    fileInfo *info[THREAD_COUNT+1] = {NULL};

    for (i=0; i<=THREAD_COUNT; i++) {
        
        info[i] = (fileInfo *)malloc(sizeof(fileInfo));
        memset(info[i], 0, sizeof(fileInfo));

        info[i]->offset = i * partsize;
        if (i < THREAD_COUNT) {
            info[i]->end = (i+1) * partsize - 1;
        } else {
            info[i]->end = fileLength - 1;
        }
        info[i]->fileptr = fileptr;
        info[i]->url = url;
        info[i]->download = 0;
        info[i]->recordfile = fp;
    }
    pInfoTable = info;

    for (i=0; i<THREAD_COUNT; i++) {
        pthread_create(&(info[i]->thid), NULL, worker, info[i]);
        usleep(1);
    }

    for (i=0; i<=THREAD_COUNT; i++) {
        pthread_join(info[i]->thid, NULL);
    }

    for (i=0; i<THREAD_COUNT; i++) {
        free(info[i]);
    }

    if (fp) {
        fclose(fp);
    }

    munmap(fileptr, fileLength);
    close(fd);

    return 0;
}

  • 首先创建一个与目标文件大小相同的文件
  • 填入空文件的最后一个字节,使得文件大小确定
  • 避免多次系统调用,使用mmap映射该文件到用户态
  • 文件分片,将每一片文件的信息如起始位置和终止位置单独记录
  • 创建多线程,每一个线程下载一个分片

4.下载过程

void * worker(void *arg) {
    fileInfo *info = (fileInfo *)arg;
    char range[64] = {0};

    if (info->recordfile) {
        fscanf(info->recordfile, "%d-%d", &info->offset, &info->end);
    }

    if (info->offset > info->end) return NULL;

    snprintf(range, 64, "%d-%d", info->offset, info->end);

    CURL *curl = curl_easy_init();

    curl_easy_setopt(curl, CURLOPT_URL, info->url); // url
	
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc); // save
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, info); 
	
	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); // progress
	curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progressFunc);
	curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, info);
	
	curl_easy_setopt(curl, CURLOPT_RANGE, range);

    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        printf("res %d\n", res);
    }

    curl_easy_cleanup(curl);

    return NULL;
}
  • 使用curl库接口,主要提供下载范围的信息

5.断点续传

  • 捕捉信号,在程序退出前保存下载进度
void signal_handler(int signum) {
    printf("signum: %d\n", signum);

    int fd = open("a.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        exit(1);
    }

    int i = 0;
    for (i=0; i<=THREAD_COUNT; i++) {
        char range[64] = {0};
        snprintf(range, 64, "%d-%d\r\n", pInfoTable[i]->offset, pInfoTable[i]->end);
        write(fd, range, strlen(range));
    }

    close(fd);
    exit(1);
}

int main(int argc, const char *argv[]) {
    if (argc != 3) {
        printf("arg error\n");
        return -1;
    }

    if (SIG_ERR == signal(SIGINT, signal_handler)) {
        perror("signal");
        return -1;
    }

    return download(argv[1], argv[2]);
}
  • main函数逻辑+信号处理实现了文件下载进度的保存
  • 保存了下载进度,在每次进行下载前都需要读取上一次的进度,继续下载
  • 在每一次保存下载进度前需要清空上一次的进度,避免混乱

推荐学习 https://xxetb.xetslk.com/s/p5Ibb

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wjq++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值