• Ubuntu 22.04 下 CURL(C++) 实现分块上传/下载文件源码


    为了帮助大家理解代码,先介绍文件上传/下载流程:

    上传文件流程说明:首先向服务器 restful api 接口 /common发送 Post 请求 ,服务器端返回 project guid。读取本地文件,按照给定 chunk_size(例如 10240 byte),不断循环向服务器 restful api 接口 /upload_chunk 发送数据,直到文件传输完成。

    视频上传后,在服务器端做 3D 建模,耗时1-2小时,故拆分步骤,首先向服务器发送请求生成 project_guid, “刷新”按钮不停获取服务器状态。这些步骤不是必须,使用者按照自己项目需求取舍。发送请求的 header, body_json 等格式,同样按照自己的项目需求设计即可。

    这是创建 project 的接口说明: 

    urlhttp://192.168.1.0:8000/common
    type

    POST

    param{
        "model_type":1,
        "project_type":0,
        "object":"plants"
    }
    return{
      "data": "85c9c7cc-18d6-11ef-818a-7d68b3e42070",
      "errcode": 0
    }

    下载文件:同样采取分块下载。

    CMakeList.txt:

    # 查找libcurl组件
    find_package(CURL REQUIRED)

    include_directories(
      include
      ${PYTHON_INCLUDE_DIRS}
      ${CURL_INCLUDE_DIRS}
    )

    target_link_libraries(${PROJECT_NAME} PRIVATE "${CURL_LIBRARY}")
    target_include_directories(${PROJECT_NAME} PRIVATE "${CURL_INCLUDE_DIR}")

    upload调用代码:

    1. void test_restapi_upload_chunk(){
    2. std::string project_id = "bee8400e-1f13-11ef-818a-7d68b3e42070";
    3. std::string file_path = "/home/coco/Documents/IMG_02391.MOV";
    4. std::string url = "http://192.168.1.87:8200/upload_chunk";
    5. try {
    6. upload_chunk(url, project_id, file_path);
    7. std::cout << "Chunk upload completed successfully." << std::endl;
    8. } catch (const std::exception& e) {
    9. std::cerr << "An error occurred: " << e.what() << std::endl;
    10. }
    11. }

     upload_chunk() 函数实现代码:

    1. void upload_chunk(const std::string& url, const std::string& project_id, const std::string& file_path, size_t chunkSize) {
    2. // curl初始化
    3. CURL* curl = curl_easy_init();
    4. if (!curl) {
    5. std::cerr << "CURL initialization failed." << std::endl;
    6. return;
    7. }
    8. // 初始化表单和头部
    9. curl_httppost* formpost = NULL;
    10. curl_httppost* lastptr = NULL;
    11. struct curl_slist* headers = NULL;
    12. // 设置 URL 和 HTTP POST 选项
    13. std::cout << "url.c_str(): " + url << std::endl;
    14. curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    15. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    16. curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
    17. // 打开文件和读取逻辑
    18. std::ifstream file(file_path, std::ios::binary);
    19. if (!file.is_open()) {
    20. std::cerr << "Failed to open file: " << file_path << std::endl;
    21. return;
    22. }
    23. std::filesystem::path path(file_path);
    24. std::string file_name = path.filename();
    25. size_t totalSize = std::filesystem::file_size(file_path);
    26. size_t offset = 0;
    27. size_t start = 0;
    28. std::cout << "totalSize: " + std::to_string(totalSize) << std::endl;
    29. // 循环读取文件并发送
    30. std::vector<char> buffer(chunkSize);
    31. while (!file.eof()) {
    32. // 读取文件内容到buffer
    33. file.read(buffer.data(), chunkSize);
    34. size_t bytesRead = file.gcount();
    35. if (bytesRead == 0) {
    36. break; // 没有更多数据可读,退出循环
    37. }
    38. // 构建project表单数据
    39. CURLFORMcode form_result = curl_formadd(
    40. &formpost,
    41. &lastptr,
    42. CURLFORM_COPYNAME, "project", // 表单字段名称
    43. CURLFORM_COPYCONTENTS, project_id.c_str(), // 表单字段值
    44. CURLFORM_END);
    45. // 为每个文件块创建一个新的表单
    46. form_result = curl_formadd(
    47. &formpost,
    48. &lastptr,
    49. CURLFORM_COPYNAME, "file", // 表单字段名称
    50. CURLFORM_BUFFERPTR, buffer.data(), // 缓冲区指针
    51. CURLFORM_BUFFERLENGTH, static_cast<long>(bytesRead), // 缓冲区长度
    52. CURLFORM_FILENAME, file_name.c_str(), // 文件名
    53. CURLFORM_CONTENTTYPE, "application/octet-stream", // 内容类型
    54. CURLFORM_END);
    55. if (form_result != CURL_FORMADD_OK) {
    56. std::cerr << "Error building file form." << std::endl;
    57. break;
    58. }
    59. // 设置请求头和POST数据
    60. std::string contentRange = std::to_string(start) + "-" + std::to_string(start + bytesRead - 1) + "/" + std::to_string(totalSize);
    61. std::cout << "contentRange: " + contentRange << std::endl;
    62. headers = curl_slist_append(headers, ("Content-Range: " + contentRange).c_str());
    63. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    64. curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
    65. // 设置回调函数和数据
    66. std::string responseContent;
    67. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, UploadWriteCallback);
    68. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseContent);
    69. // 执行请求
    70. CURLcode res = curl_easy_perform(curl);
    71. if (res != CURLE_OK) {
    72. std::cerr << "CURL error: " << curl_easy_strerror(res) << std::endl;
    73. break;
    74. }
    75. // 更新下一个块的起始位置
    76. start += bytesRead;
    77. // 处理响应数据
    78. if (!responseContent.empty()) {
    79. std::cout << responseContent << std::endl;
    80. }
    81. // 清理headers和表单
    82. curl_slist_free_all(headers);
    83. headers = nullptr;
    84. curl_formfree(formpost);
    85. formpost = NULL;
    86. lastptr = NULL;
    87. }
    88. // 清理资源
    89. curl_formfree(formpost);
    90. curl_easy_cleanup(curl);
    91. file.close();
    92. }

    download 调用代码:

    1. void test_restapi_download_chunk(){
    2. char cwd[PATH_MAX];
    3. if (getcwd(cwd, sizeof(cwd)) != NULL) {
    4. std::cout << "getcwd(): " << cwd << std::endl;
    5. } else {
    6. perror("getcwd() error");
    7. }
    8. QString currentPath = QDir::currentPath();
    9. std::cout << "Current working directory: " << currentPath.toStdString() << std::endl;
    10. std::string url = "http://192.168.1.87:8200/download_chunk"; // 替换为实际的Flask服务器URL
    11. std::string project = "85c9c7cc-18d6-11ef-818a-7d68b3e42070"; // 替换为实际项目名称
    12. std::string output_path = "/home/coco/Downloads/point_cloud.ply"; // 替换为实际保存文件的路径
    13. size_t start = 0; // 替换为实际起始字节
    14. size_t end = 1024; // 替换为实际结束字节,这里假设DOWNLOAD_RANGE的值为102400
    15. try {
    16. if (FileSystemUtils::exists(output_path)) {
    17. std::cout << output_path + "already exists." << std::endl;
    18. return ;
    19. }
    20. download_chunk(url, project, output_path, start, end);
    21. std::cout << "Chunk download completed successfully." << std::endl;
    22. } catch (const std::exception& e) {
    23. std::cerr << "An error occurred: " << e.what() << std::endl;
    24. }
    25. }

     download_chunk()函数代码实现:

    1. int download_chunk(const std::string& url, const std::string& project_id, const std::string& output_path, size_t start, size_t end=10240) {
    2. // 创建 curl 句柄
    3. CURL *curl = curl_easy_init();
    4. if (!curl) {
    5. std::cerr << "CURL initialization failed." << std::endl;
    6. return 1;
    7. }
    8. // 保存文件,确保以追加模式打开
    9. std::ofstream outfile(output_path, std::ios::binary | std::ios::app);
    10. if (!outfile.is_open()) {
    11. std::cerr << "Cannot open file: " << output_path << std::endl;
    12. return 1;
    13. }
    14. // 设置请求的URL
    15. curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    16. curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
    17. //curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    18. //curl_easy_setopt(curl, CURLOPT_HTTPHEADER, nullptr);
    19. // 设置请求body: project_id
    20. std::string json_data = nlohmann::json{{"project", project_id}}.dump();
    21. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data.c_str());
    22. CURLcode res;
    23. size_t total_size = -1;
    24. size_t content_length;
    25. bool is_total_size_calculated = false;
    26. std::string response_content;
    27. std::string response_headers;
    28. std::string range_header = "Range: " + std::to_string(start) + "-" + std::to_string(end);
    29. std::cout << "first range_header: " << range_header << std::endl << std::endl;
    30. int i = 1;
    31. struct curl_slist *headers = nullptr;
    32. try {
    33. do {
    34. std::cout << "第" << std::to_string(i) <<"次循环:" << std::endl;
    35. i = i + 1;
    36. // 设置请求头, 包括Range和Content-Type
    37. headers = curl_slist_append(headers, "Content-Type: application/json");
    38. headers = curl_slist_append(headers, range_header.c_str());
    39. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    40. // 设置响应头回调函数
    41. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderCallback);
    42. curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response_headers);
    43. // 设置写入回调函数
    44. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DownloadWriteCallback);
    45. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_content);
    46. // 执行请求
    47. res = curl_easy_perform(curl);
    48. if (res != CURLE_OK) {
    49. std::cerr << "Download error: " << curl_easy_strerror(res) << std::endl;
    50. curl_slist_free_all(headers);
    51. curl_easy_cleanup(curl);
    52. //curl_global_cleanup();
    53. if (outfile.is_open()) {
    54. outfile.close(); // 关闭文件
    55. }
    56. return 1;
    57. } else {
    58. // 响应头存储在response_header中,解析response_header来获取需要的信息
    59. std::cout << "Response headers: "<< std::endl << response_headers << std::endl;
    60. // 解析响应头, Content-Range, Content-Length
    61. std::istringstream header_stream(response_headers);
    62. std::string header_line;
    63. while (getline(header_stream, header_line)) {
    64. if (header_line.find("Content-Range:") == 0) {
    65. std::cout << "header_line: " << header_line << std::endl;
    66. if (!is_total_size_calculated ) {
    67. size_t pos = header_line.find('/');
    68. std::string total_size_str = header_line.substr(pos + 1);
    69. total_size = std::stoull(total_size_str);
    70. std::cout << "total_size: " << total_size << std::endl;
    71. is_total_size_calculated = true;
    72. }
    73. } else if (header_line.find("Content-Length:") == 0) {
    74. size_t pos = header_line.find(':');
    75. content_length = std::stoul(header_line.substr(pos + 2));
    76. std::cout << "content_length: " << content_length << std::endl;
    77. // 更新下一次请求的范围
    78. start += content_length;
    79. end = std::min(end + content_length, total_size);
    80. // 更新range_header
    81. range_header = "Range: bytes=" + std::to_string(start) + "-" + std::to_string(end);
    82. std::cout << "request range_header: " << range_header << std::endl;
    83. std::cout << "start: " << start << std::endl;
    84. std::cout << "end: " << end << std::endl << std::endl;
    85. }
    86. }
    87. // 将 responseContent 写入到文件
    88. outfile.write(response_content.c_str(), response_content.length());
    89. curl_slist_free_all(headers);
    90. headers = nullptr;
    91. response_headers.clear();
    92. response_content.clear();
    93. }
    94. } while (start < total_size);
    95. }catch (const std::bad_alloc& e) {
    96. std::cerr << "Memory allocation failed: " << e.what() << std::endl;
    97. if (headers) {
    98. curl_slist_free_all(headers);
    99. }
    100. curl_easy_cleanup(curl);
    101. if (outfile.is_open()) {
    102. outfile.close();
    103. }
    104. return 1;
    105. }
    106. curl_easy_cleanup(curl);
    107. if (outfile.is_open()) {
    108. outfile.close(); // 关闭文件
    109. }
    110. return 0;
    111. }

  • 相关阅读:
    【人工智能数学基础】几何解释——最小二乘法
    Linux友人帐之进程管理
    记一次 .NET某报关系统 非托管泄露分析
    通信原理学习笔记3-4:数字通信系统性能指标(带宽、信噪比Eb/N0、可靠性与误码率、有效性与频谱利用率)
    CS5263数据手册|CS5263替代PS176|DP转HDMI2.0芯片设计资料
    端子排中间继电器WZY-400W
    linux 使用 squid
    openssl客户端编程:一个不起眼的函数导致的SSL会话失败问题
    用Unity实现景深效果
    隐藏饼图的legend,重写legend列表。
  • 原文地址:https://blog.csdn.net/coco_1998_2/article/details/139613148