本文介绍c语言实现多线程下载,使用 curl库
需求分析
对于一个文件的下载,我们有多种情况需要考虑:
1.单线程下载还是多线程下载
2.如果是多线程下载,如何保证下载结果正确
3.能否实现断点续传
4.能否实现多文件下载
设计
1.使用curl库
-
优点是功能和接口成熟
-
缺点是不能跨平台
2.使用多线程下载
- 将目标文件拆分成多份,每个线程下载各自的部分,互不影响
- 即分片下载
3.注册信号来捕捉程序退出,在退出前保存当前下载进度到文件,每次下载先读取文件,获取上次进度,再下载
- 配合curl接口,保存每个线程的下载范围
- 在每次保存进度文件时先清空上一次的文件,避免混乱
4.多文件下载需要fork多进程,本文暂不实现
实现
1.首先需要一个结构体来保存目标文件的一些信息,便于管理
typedef struct fileInfo {const char *url; // 下载文件的URLchar *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); // urlcurl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc); // savecurl_easy_setopt(curl, CURLOPT_WRITEDATA, info); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); // progresscurl_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