场景,构建一个下载类组件,基于libcurl,达到正常下载、分片传输、断点续传等功能,同时保证组件的健壮性、对极限情况的兼容性、对上层业务回抛信息的完善
分片传输+断点的实现对本次任务有要求限制,一般来说:针对大文件传输服务端都会进行该功能的配置
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;
}
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)

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配合已知的文件长度,定位到待写入的位置,信息依次写入。注意:一旦某个字节出错,则会导致全部下载失败!
7.9.6版本后引入的multi接口,可以一次针对多个easy_curl句柄进行操作,类似多路复用IO。个人的设计中,将多线程替换为该方式,可以很大程度上降低复杂度。
libcurl的multi interface,7.9.6之后引入。个人分析为:easy_curl在开始执行后会阻塞当前线程,因此通常使用多线程来实现下载,而multi interface可以对多个句柄共同操作,同时这个操作可以是异步的,避免了主线程阻塞。但对于任务的结束,仍需要遍历任务堆栈。
思路:
该回调函数是分片传输的核心,具体分为以下几个步骤
实际使用,需要从整个框架层面设计,包含主流程、网络错误处理、IO错误处理、业务层回抛信息、异步操作等内容综合设计