libcurl与分片传输、断点续传相关研究

15 篇文章 0 订阅
11 篇文章 2 订阅
本文介绍了基于libcurl构建一个下载组件,实现大文件的分片传输和断点续传功能。通过服务端请求获取文件总长度,检查是否支持分段传输。使用多任务下载和内存映射临时文件,结合libcurl的multi接口进行异步操作。在断点续传时,读取临时文件信息并重新构建下载任务。讨论了健壮性、错误处理和上层业务的感知性等关键问题。
摘要由CSDN通过智能技术生成

分片传输、断点续传相关研究

场景,构建一个下载类组件,基于libcurl,达到正常下载、分片传输、断点续传等功能,同时保证组件的健壮性、对极限情况的兼容性、对上层业务回抛信息的完善

对本次任务的前置校验操作

分片传输+断点的实现对本次任务有要求限制,一般来说:针对大文件传输服务端都会进行该功能的配置

  1. 通过服务端请求拿到文件总长度
    给出一个demo例子,具体需要根据业务场景添加
bool getFileLength(const std::string url, curl_off_t& fileLength) {
    bool retValue = false;
    for (int retrytime = 0; retrytime < 5; retrytime++) {
        CURL* curlHandle = curl_easy_init();
        curl_easy_setopt(curlHandle, CURLOPT_SHARE, sharednsHandle);
        curl_easy_setopt(curlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5);
        curl_easy_setopt(curlHandle, CURLOPT_CUSTOMREQUEST, "GET");
        curl_easy_setopt(curlHandle, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curlHandle, CURLOPT_HEADER, 1);
        curl_easy_setopt(curlHandle, CURLOPT_NOBODY, 1);
        curl_easy_setopt(curlHandle, CURLOPT_TIMEOUT, MM_TIMEOUT);
        curl_easy_setopt(curlHandle, CURLOPT_NOSIGNAL, 1L);
        CURLcode code = curl_easy_perform(curlHandle);
        if (code == CURLE_OK) {
         	curl_easy_getinfo(curlHandle,
                                  CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
                                  &fileLength);
            retValue = true;
            break;
        }
	}
	return retValue;
}
  1. 通过请求得到的HEADERDATA中是否含有Content-Range: bytes、Accept-Ranges: bytes确定该下载是否支持分段传输
bool useMutilDownload(std::string url) {
    bool ret = false;

    for (int retryTime = 0; retryTime < 5; retryTime++) {
        CURL* curlHandle = curl_easy_init();
        curl_easy_setopt(curlHandle, CURLOPT_SHARE, sharednsHandle);
        curl_easy_setopt(curlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5);
        curl_easy_setopt(curlHandle, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curlHandle, CURLOPT_HEADER, 1);
        curl_easy_setopt(curlHandle, CURLOPT_NOBODY, 1);
        std::string strHeader;
        curl_easy_setopt(curlHandle, CURLOPT_HEADERDATA, &strHeader);
        curl_easy_setopt(curlHandle, CURLOPT_HEADERFUNCTION,
                         &LibcurlMultiThread::headerInfo);
        curl_easy_setopt(curlHandle, CURLOPT_RANGE, "0-");
        curl_easy_setopt(curlHandle, CURLOPT_TIMEOUT, MM_TIMEOUT);
        curl_easy_setopt(curlHandle, CURLOPT_NOSIGNAL, 1L);
        CURLcode code = curl_easy_perform(curlHandle);
        if (code == CURLE_OK) {
            ret =
                ((strHeader.find("Content-Range: bytes") !=
                  std::string::npos) ||
                 (strHeader.find("Accept-Ranges: bytes") != std::string::npos));
            break;
        }
    }
    return ret;
}

针对多任务下载及断点传输文件的设计

注:所有的操作,保证使用curl_off_t(__int64)

  • . 使用.dltmp文件进行过度,用内存映射创建临时文件,大小为前置工作中已获取的长度+分片传输自定义信息

文件设计

beginPos:该块开始的位置,blockSize:该块下载内容大小,recvSize:该块下载已完成的大小

最优线程数计算:min(文件长度/最优下载块大小,自定义最大线程数量)

例:文件长度为2001byte,将分为5个任务下载,临时信息长度5 * 3 * sizeof(curl_off_t)+sizeof(curl_off_t);5块分别下载大小400、400、400、400、401,则第一块的信息是,beginPos 0,blockSize 400,recvSize 0,第二块beginPos 400,blcokSize 400,recvSize 400…

实际开发中,可自定义数据结构,用于存储若干个任务块的信息。同时在开始传输前,将临时文件尾部的信息进行准确填写,具体操作为_fseeki64配合已知的文件长度,定位到待写入的位置,信息依次写入。注意:一旦某个字节出错,则会导致全部下载失败!

  • . 使用curl_multi_init()替换多线程

7.9.6版本后引入的multi接口,可以一次针对多个easy_curl句柄进行操作,类似多路复用IO。个人的设计中,将多线程替换为该方式,可以很大程度上降低复杂度。

libcurl的multi interface,7.9.6之后引入。个人分析为:easy_curl在开始执行后会阻塞当前线程,因此通常使用多线程来实现下载,而multi interface可以对多个句柄共同操作,同时这个操作可以是异步的,避免了主线程阻塞。但对于任务的结束,仍需要遍历任务堆栈。

思路:

  1. n个线程变为获取n个easy_curl任务句柄,配合分片传输中的beginPos、blockSize、recvSize信息,确定该句柄执行的下载任务范围;
  2. 若干句柄使用同一个写入文件回调函数来避免同步的问题
  3. 将若干句柄加入multi中,异步启动,不阻塞主线程;
  • 文件写入回调函数

该回调函数是分片传输的核心,具体分为以下几个步骤

  1. 多个任务要使用同一个回调来避免IO等问题
  2. 根据自定义的数据结构(主要存放每个任务的信息,包括beginPos,blockSize,recvSize等),将文件句柄_fseeki64重新定位,值为beginPos+recvSize,再将本次得到的buffer进行写入操作;
  3. 重新定位到该次写入任务对应的块位置,为达到这个目的,也许需要在自定义的数据结构中加入相关信息实现,当_fseeki64定位到块的位置后,将beginPos、blockSize、recvSize进行值更新

断点流程

  • 根据临时文件的信息判断是否可续传
  1. 首先得到文件长度,同从服务端请求的长度比对,若不同则可能文件已更新或损坏,需要重新构建下载任务
  2. 根据文件长度,得到临时信息相关的位置,将文件句柄定位至该处,读取每一个块的信息(beginPos、blockSize、recvSize),更新自定义数据结构
  3. 根据读取的内容,构建本次下载任务对应块的偏移量(beginPos,blockSize-recvSize),将多个easy_curl句柄加入mutil_curl,断点传输

核心流程总结梳理

  1. 本次下载的前置操作,包括请求本次下载文件长度、是否启动分片传输
  2. 临时文件的构建,使用内存映射创建临时文件格式为.dltmp,大小为请求的文件长度+分片传输信息长度(sizeof(cur_off_t))+n* 3 * sizeof(cur_off_t)
  3. 填充自定义的数据结构,并将内容写入临时文件的后缀信息中
  4. 构建若干个cur_easy句柄任务,设置每个句柄下载任务区间,将其加入到multi句柄
  5. 为若干句柄构建同一个文件写入回调,在回调中,重定向文件写入指针位置,写入本次buffer数据;再次重新定位文件句柄到对应任务的尾部信息位置,更新数据结构和文件尾部信息内容(recvSize);
  6. 当断点续传时,首先读取尾部文件确定文件是否可用,后根据尾部的块信息定义本次下载任务每个块对应的区间
  7. 利用mutil_curl进行任务异步进行

待解决

  1. 健壮性,任何一个字节的出错将导致整个文件的出错,而实际应用中网络环境复杂、机器IO性能差异化明显,因此该设计使用需要构建完善的边界极限处理机制。
  2. 上层业务的感知性,libcurl的easy_curl可以得到错误码,只针对本块传输的下载和写入,而对于所有的读取、临时信息解析、临时信息写入、IO等操作,需要构建一套可回抛给上层业务的错误码机制
  3. 异步问题,虽然使用mutil代替线程已经很大程度降低了复杂度,但获取当前的下载状态信息等仍需要轮询mutil本身的消息堆栈,而该操作不能影响主线程,因此仍需要异步任务来调用curl_multi_poll或curl_multi_perform来实现,作为组件的效果不如直接对接具体业务。(组件黑盒化,对外界无感知)

实际使用,需要从整个框架层面设计,包含主流程、网络错误处理、IO错误处理、业务层回抛信息、异步操作等内容综合设计

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值