c++使用libcurl实现断点续传

libcurl实现断点续传大致需要两个关键点:

  • 1:使用GetFileSize接口获取本地已缓存文件的大小。
  • 2:通过curl_easy_setopt接口的CURLOPT_RESUME_FROM_LARGE参数,将获取的大小设置到开始下载的起点。

下面demo代码仅提供思路,需要优化才能使用于线上项目(比如本地缓存文件的有效性就是必须要考虑的一个问题)

实现的demo代码:

void OpenConsole() {
  AllocConsole();
  freopen("CONIN$", "r+t", stdin);
  freopen("CONOUT$", "w+t", stdout);
}

struct DownloadAttr {
  curl_off_t    file_size;
  CURL*         curl;
};

size_t WriteCallback(char * ptr, size_t size, size_t nmemb, void * userdata) {
  HANDLE& hFile = (HANDLE)userdata;
  DWORD write_byte = 0;
  if (!::WriteFile(hFile, ptr, size * nmemb, &write_byte, NULL))
    return 0;

  return write_byte;
}

int ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
  if (dltotal == 0)
    return 0;

  DownloadAttr* download_attr = (DownloadAttr*)clientp;
  int progress = (double)(dlnow + download_attr->file_size) / (dltotal + download_attr->file_size) * 100;
  static int last_progress = 0;
  if (last_progress != progress) {
    last_progress = progress;
    double speed = 0;
    curl_easy_getinfo(download_attr->curl, CURLINFO_SPEED_DOWNLOAD, &speed);
    speed /= 1024;

    std::cout << "已下载:" << progress << "%" << " 下载速度:" << speed << "kb/s" << std::endl;
  }

  return 0;
}

bool BreakpointResumeDownload(std::string url, std::wstring save_path) {
  HANDLE hFile = ::CreateFile(save_path.c_str(), GENERIC_READ | GENERIC_WRITE, 
    0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
  if (hFile == INVALID_HANDLE_VALUE)
    return false;

  CURL* curl = curl_easy_init();
  if (curl == nullptr) {
    ::CloseHandle(hFile);
    return false;
  }

  curl_off_t file_size = ::GetFileSize(hFile, nullptr);
  ::SetFilePointer(hFile, 0, 0, FILE_END);
  DownloadAttr download_attr = { file_size,curl };

  curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, hFile);
  curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback);
  curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &download_attr);
  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, file_size);

  CURLcode curl_code = curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  ::CloseHandle(hFile);

  if (curl_code != CURLE_OK) {
    ::DeleteFile(save_path.c_str());
    return false;
  }

  return true;
}

int WINAPI _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) {
  OpenConsole();
  ::CoInitialize(nullptr);
  curl_global_init(CURL_GLOBAL_ALL);

  std::cout << "开始下载:" << std::endl;
  BreakpointResumeDownload(
    "https://dldir1.qq.com/music/clntupate/QQMusicSetup.exe",
    L"D:\\test.exe");
  std::cout << "下载完成:" << std::endl;

  curl_global_cleanup();
  ::CoUninitialize();

  getchar();
  return 0;
}

demo演示:

为演示续传,演示图为下载开头和结尾部分,中间部分没有截图