C语言实现多线程下载

本文介绍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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/50309.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端创建仓库的详细步骤

第一步点击号新建仓库 第二步输入完仓库名称路径会自己出来然后点击创建 第三步在自己创建的文件夹右键点击GIt Bash Here 第四步把我框的这些一个一个的输在Git Bash Here中每输入一个回车一个 第五步全部输入完以后CtrlF5自动刷新下就好了 然后文件夹就会有.git了

机器视觉12-相机

相机 作用: 工业相机 是 机器视觉系统 的重要组成部分 最本质的功能就是通过CCD或CMOS成 像传感器将镜头产生的光信号转变为 有序的电信号&#xff0c;并将这些信息通过相 应接口传送到计算机主机 工业相机分类 目前业内没有对相机进行明确的分类定义&#xff0c; 以下分类是…

Python 学习中的 API,如何调用API ?

1.1 API的定义 API&#xff0c;全称是Application Programming Interface&#xff08;应用程序编程接口&#xff09;。它是一组定义好的协议和工具&#xff0c;用于在软件应用程序之间进行通信。API可以简化软件开发&#xff0c;使不同的应用程序能够相互协作。它是软件开发中…

数字车间与智能工厂:区别、联系与制造业的未来转型

数字车间和智能工厂在制造业中扮演着重要角色&#xff0c;它们之间存在明显的区别和紧密的联系。以下是对两者区别和联系的详细阐述&#xff1a; 一、区别 定义与范围 数字车间&#xff1a;数字车间是指通过信息化技术、智能化装备和数据化管理等手段&#xff0c;实现生产过程全…

【Python系列】Python 程序的优雅退出:使用`sys.exit()`控制程序终止

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

笔记本电脑怎么录屏?5个小技巧(2024最全)

在今天&#xff0c;录屏功能已经不再是专业人士的专属&#xff0c;而是融入了普通人的日常生活与工作之中。想要记录游戏的精彩瞬间、分享软件的操作教程&#xff0c;或是保存屏幕上的重要信息&#xff0c;录屏都能帮你一键搞定。那么&#xff0c;对于我们这些日常使用笔记本电…

初始K8s

K8S 基本概念: K8S 的全称为 Kubernetes (K12345678S)&#xff0c;PS&#xff1a;“嘛&#xff0c;写全称也太累了吧&#xff0c;不如整个缩写”。 作用&#xff1a; 用于自动部署、扩展和管理“容器化&#xff08;containerized&#xff09;应用程序”的开源系统。 可以理解成…

火狐浏览器怎么切换ip:详细步骤与注意事项

随着互联网的飞速发展&#xff0c;网络环境的复杂性和安全性问题日益凸显。对于需要保护个人隐私、突破地域限制或进行网络测试的用户来说&#xff0c;切换IP地址成为了一项重要的技能。火狐浏览器&#xff0c;作为一款备受欢迎的开源浏览器&#xff0c;凭借其强大的自定义功能…

Kafka 消费者启动后与服务器的交互流程

Kafka 消费者启动后与服务器的交互流程涉及多个关键步骤&#xff0c;主要包括初始化、查找组协调器、加入消费者组、分区分配、心跳维持、拉取数据和提交偏移量等。以下是详细的流程说明&#xff1a; 1. 初始化消费者 创建消费者实例&#xff1a;应用程序通过调用KafkaConsum…

PHP表单必需字段

在PHP中处理表单时&#xff0c;确保必填字段被正确填写是非常重要的。这通常涉及到在客户端&#xff08;使用HTML5&#xff09;和服务器端&#xff08;使用PHP&#xff09;进行验证。以下是一个关于PHP表单必需字段的详细教程&#xff0c;包括如何在客户端和服务器端进行验证。…

【计算机网络】TCP和UDP的封装以及案例

TCP和UDP的封装以及案例 背景知识TCP实现UDP实现封装Network用NetWork再次实现TCP和UDP小知识点 背景知识 TCP&#xff1a;传输控制协议&#xff08;Transmission Control Protocol&#xff09; UDP&#xff1a;用户数据报协议 &#xff08;User Datagram Protocol&#xff09…

Spring Bean的初始化过程

在Spring框架中&#xff0c;Bean是应用程序的基本构建块。每个Bean都是由Spring IoC容器管理的对象实例&#xff0c;用于封装业务逻辑或资源。理解Spring Bean的初始化过程对于有效地使用和配置Spring框架至关重要。本文将详细解释Spring Bean的生命周期&#xff0c;包括其创建…

AI的欺骗游戏:揭示多模态大型语言模型的易受骗性

人工智能咨询培训老师叶梓 转载标明出处 多模态大型语言模型&#xff08;MLLMs&#xff09;在处理包含欺骗性信息的提示时容易生成幻觉式响应。尤其是在生成长响应时&#xff0c;仍然是一个未被充分研究的问题。来自 Apple 公司的研究团队提出了MAD-Bench&#xff0c;一个包含8…

Spring的优缺点?

Spring的优缺点 直接回答相关的Spring的特点&#xff1a; IOC AOP 事务 简化开发&#xff1a; 容易集成JDBCTemplateRestTemplate&#xff08;接口远程调用&#xff09;邮件发送相关异步消息请求支持 更加深入就讲源码了 优点&#xff1a; 方便解耦&#xff0c;简化开发…

网站打不开怎么办,收藏以备不时之需

DNS设置示范教程 部分地区有使用移动网络的小伙伴们吐槽无法访问部分网站的情况&#xff0c;同样的网站&#xff0c;使用电信和联通的用户就能正常访问。 这其实有很大几率是由于运营商的网络问题导致的&#xff0c;容易出现网站打不开的结果。 要解决移动网络无法访问的情况…

docker 部署mysql nginx redis

设置镜像 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": ["https://tddq0ov6.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload 重启 sudo systemctl restart docker mysql mkdir /docker/my…

[React]如何提高大数据量场景下的Table性能?

[React]如何提高大数据量场景下的Table性能&#xff1f; 两个方向&#xff1a;虚拟列表&#xff0c;发布订阅 虚拟列表 虚拟列表实际上只对可视区域的数据项进行渲染 可视区域&#xff08;visibleHeight&#xff09;: 根据屏幕可视区域动态计算或自定义固定高度数据渲染项&…

python_合并同一个文件夹下的excel文件

python_合并同一个文件夹下的excel文件 import os import glob import pandas as pddef merge_excel_sheets(input_folder, output_file):# 创建一个空的 DataFrame 用于存储所有数据combined_data pd.DataFrame()# 获取指定文件夹内所有的 Excel 文件excel_files glob.glob…

el-select下拉数据量太大,改成滚动加载数据

问题描述&#xff1a;当接口返回下拉数据量特别大的时候&#xff0c; 页面会卡顿&#xff0c; 下面采用下拉加载指定数据的方式来优化。 <template><el-selectv-model"value"filterableplaceholder"Select"v-focus"loadData(loadNumber)&qu…

(面试必看!)一些和多线程相关的面试考点

文章导读 引言考点1. CAS 指令&#xff08;重点&#xff09;一、什么是CAS二、CAS 的优点三、CAS 的缺点四、ABA问题五、相关面试题 考点2. 信号量&#xff08;semaphore&#xff09;一、基本概念二、信号量的主要操作三、信号量的应用四、相关面试题 考点3、CountDownLatch 类…