为了帮助大家理解代码,先介绍文件上传/下载流程:
上传文件流程说明:首先向服务器 restful api 接口 /common发送 Post 请求 ,服务器端返回 project guid。读取本地文件,按照给定 chunk_size(例如 10240 byte),不断循环向服务器 restful api 接口 /upload_chunk 发送数据,直到文件传输完成。
视频上传后,在服务器端做 3D 建模,耗时1-2小时,故拆分步骤,首先向服务器发送请求生成 project_guid, “刷新”按钮不停获取服务器状态。这些步骤不是必须,使用者按照自己项目需求取舍。发送请求的 header, body_json 等格式,同样按照自己的项目需求设计即可。
这是创建 project 的接口说明:
url | http://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}")
上传文件调用代码:
void test_restapi_upload_chunk(){ std::string project_id = "bee8400e-1f13-11ef-818a-7d68b3e42070"; std::string file_path = "/home/coco/Documents/IMG_02391.MOV"; std::string url = "http://192.168.1.87:8200/upload_chunk"; try { upload_chunk(url, project_id, file_path); std::cout << "Chunk upload completed successfully." << std::endl; } catch (const std::exception& e) { std::cerr << "An error occurred: " << e.what() << std::endl; } }
upload_chunk() 函数实现代码:
void upload_chunk(const std::string& url, const std::string& project_id, const std::string& file_path, size_t chunkSize) { // curl初始化 CURL* curl = curl_easy_init(); if (!curl) { std::cerr << "CURL initialization failed." << std::endl; return; } // 初始化表单和头部 curl_httppost* formpost = NULL; curl_httppost* lastptr = NULL; struct curl_slist* headers = NULL; // 设置 URL 和 HTTP POST 选项 std::cout << "url.c_str(): " + url << std::endl; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); // 打开文件和读取逻辑 std::ifstream file(file_path, std::ios::binary); if (!file.is_open()) { std::cerr << "Failed to open file: " << file_path << std::endl; return; } std::filesystem::path path(file_path); std::string file_name = path.filename(); size_t totalSize = std::filesystem::file_size(file_path); size_t offset = 0; size_t start = 0; std::cout << "totalSize: " + std::to_string(totalSize) << std::endl; // 循环读取文件并发送 std::vectorbuffer(chunkSize); while (!file.eof()) { // 读取文件内容到buffer file.read(buffer.data(), chunkSize); size_t bytesRead = file.gcount(); if (bytesRead == 0) { break; // 没有更多数据可读,退出循环 } // 构建project表单数据 CURLFORMcode form_result = curl_formadd( &formpost, &lastptr, CURLFORM_COPYNAME, "project", // 表单字段名称 CURLFORM_COPYCONTENTS, project_id.c_str(), // 表单字段值 CURLFORM_END); // 为每个文件块创建一个新的表单 form_result = curl_formadd( &formpost, &lastptr, CURLFORM_COPYNAME, "file", // 表单字段名称 CURLFORM_BUFFERPTR, buffer.data(), // 缓冲区指针 CURLFORM_BUFFERLENGTH, static_cast (bytesRead), // 缓冲区长度 CURLFORM_FILENAME, file_name.c_str(), // 文件名 CURLFORM_CONTENTTYPE, "application/octet-stream", // 内容类型 CURLFORM_END); if (form_result != CURL_FORMADD_OK) { std::cerr << "Error building file form." << std::endl; break; } // 设置请求头和POST数据 std::string contentRange = std::to_string(start) + "-" + std::to_string(start + bytesRead - 1) + "/" + std::to_string(totalSize); std::cout << "contentRange: " + contentRange << std::endl; headers = curl_slist_append(headers, ("Content-Range: " + contentRange).c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); // 设置回调函数和数据 std::string responseContent; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, UploadWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseContent); // 执行请求 CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { std::cerr << "CURL error: " << curl_easy_strerror(res) << std::endl; break; } // 更新下一个块的起始位置 start += bytesRead; // 处理响应数据 if (!responseContent.empty()) { std::cout << responseContent << std::endl; } // 清理headers和表单 curl_slist_free_all(headers); headers = nullptr; curl_formfree(formpost); formpost = NULL; lastptr = NULL; } // 清理资源 curl_formfree(formpost); curl_easy_cleanup(curl); file.close(); }
download 调用部分代码:
void test_restapi_download_chunk(){ char cwd[PATH_MAX]; if (getcwd(cwd, sizeof(cwd)) != NULL) { std::cout << "getcwd(): " << cwd << std::endl; } else { perror("getcwd() error"); } QString currentPath = QDir::currentPath(); std::cout << "Current working directory: " << currentPath.toStdString() << std::endl; std::string url = "http://192.168.1.87:8200/download_chunk"; // 替换为实际的Flask服务器URL std::string project = "85c9c7cc-18d6-11ef-818a-7d68b3e42070"; // 替换为实际项目名称 std::string output_path = "/home/coco/Downloads/point_cloud.ply"; // 替换为实际保存文件的路径 size_t start = 0; // 替换为实际起始字节 size_t end = 1024; // 替换为实际结束字节,这里假设DOWNLOAD_RANGE的值为102400 try { if (FileSystemUtils::exists(output_path)) { std::cout << output_path + "already exists." << std::endl; return ; } download_chunk(url, project, output_path, start, end); std::cout << "Chunk download completed successfully." << std::endl; } catch (const std::exception& e) { std::cerr << "An error occurred: " << e.what() << std::endl; } }
download_chunk()函数代码实现:
int download_chunk(const std::string& url, const std::string& project_id, const std::string& output_path, size_t start, size_t end=10240) { // 创建 curl 句柄 CURL *curl = curl_easy_init(); if (!curl) { std::cerr << "CURL initialization failed." << std::endl; return 1; } // 保存文件,确保以追加模式打开 std::ofstream outfile(output_path, std::ios::binary | std::ios::app); if (!outfile.is_open()) { std::cerr << "Cannot open file: " << output_path << std::endl; return 1; } // 设置请求的URL curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET"); //curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); //curl_easy_setopt(curl, CURLOPT_HTTPHEADER, nullptr); // 设置请求body: project_id std::string json_data = nlohmann::json{{"project", project_id}}.dump(); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data.c_str()); CURLcode res; size_t total_size = -1; size_t content_length; bool is_total_size_calculated = false; std::string response_content; std::string response_headers; std::string range_header = "Range: " + std::to_string(start) + "-" + std::to_string(end); std::cout << "first range_header: " << range_header << std::endl << std::endl; int i = 1; struct curl_slist *headers = nullptr; try { do { std::cout << "第" << std::to_string(i) <<"次循环:" << std::endl; i = i + 1; // 设置请求头, 包括Range和Content-Type headers = curl_slist_append(headers, "Content-Type: application/json"); headers = curl_slist_append(headers, range_header.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // 设置响应头回调函数 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderCallback); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response_headers); // 设置写入回调函数 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DownloadWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_content); // 执行请求 res = curl_easy_perform(curl); if (res != CURLE_OK) { std::cerr << "Download error: " << curl_easy_strerror(res) << std::endl; curl_slist_free_all(headers); curl_easy_cleanup(curl); //curl_global_cleanup(); if (outfile.is_open()) { outfile.close(); // 关闭文件 } return 1; } else { // 响应头存储在response_header中,解析response_header来获取需要的信息 std::cout << "Response headers: "<< std::endl << response_headers << std::endl; // 解析响应头, Content-Range, Content-Length std::istringstream header_stream(response_headers); std::string header_line; while (getline(header_stream, header_line)) { if (header_line.find("Content-Range:") == 0) { std::cout << "header_line: " << header_line << std::endl; if (!is_total_size_calculated ) { size_t pos = header_line.find('/'); std::string total_size_str = header_line.substr(pos + 1); total_size = std::stoull(total_size_str); std::cout << "total_size: " << total_size << std::endl; is_total_size_calculated = true; } } else if (header_line.find("Content-Length:") == 0) { size_t pos = header_line.find(':'); content_length = std::stoul(header_line.substr(pos + 2)); std::cout << "content_length: " << content_length << std::endl; // 更新下一次请求的范围 start += content_length; end = std::min(end + content_length, total_size); // 更新range_header range_header = "Range: bytes=" + std::to_string(start) + "-" + std::to_string(end); std::cout << "request range_header: " << range_header << std::endl; std::cout << "start: " << start << std::endl; std::cout << "end: " << end << std::endl << std::endl; } } // 将 responseContent 写入到文件 outfile.write(response_content.c_str(), response_content.length()); curl_slist_free_all(headers); headers = nullptr; response_headers.clear(); response_content.clear(); } } while (start < total_size); }catch (const std::bad_alloc& e) { std::cerr << "Memory allocation failed: " << e.what() << std::endl; if (headers) { curl_slist_free_all(headers); } curl_easy_cleanup(curl); if (outfile.is_open()) { outfile.close(); } return 1; } curl_easy_cleanup(curl); if (outfile.is_open()) { outfile.close(); // 关闭文件 } return 0; }
还没有评论,来说两句吧...