C++实现HTTP GET响应发送.gz文件遇损坏及传输原理问询
问题分析与解决方案
你的代码核心错误在于每次分块发送时都生成了一个完整的HTTP响应,包括重复的状态行、响应头,这会导致客户端接收到的内容是多个HTTP响应的拼接,而非单一的.gz文件数据,最终造成文件损坏。
关键错误点
send_chunk函数每次都创建http::response对象并发送完整响应,客户端会将这些重复的响应头和body拼接在一起,破坏了.gz文件的二进制结构。- 重复设置
Content-Length头,且每次都传入剩余文件大小,完全不符合HTTP协议规范——Content-Length应该在响应头中仅设置一次,对应整个文件的总大小。 send_file_in_chunks的循环逻辑错误,每次发送后修改file_size并传入下一次发送,进一步加剧了响应头的错误。
正确的发送方式
不需要解压.gz文件,直接发送原始二进制内容即可,客户端保存接收到的二进制数据就是完整可用的.gz文件。正确的实现分为两种方式:
方式一:已知文件总大小,发送完整响应头后分块传body
先发送一次包含正确头部的HTTP响应,然后循环将文件内容分块写入socket,不需要每次创建新的response对象。
修正后的代码示例:
constexpr std::size_t chunk_size = 1024 * 1024; // 1 MB chunk size // 读取文件块的函数保留不变 std::vector<char> readChunk(std::ifstream& file, size_t chunkSize) { std::vector<char> buffer(chunkSize); file.read(buffer.data(), chunkSize); buffer.resize(file.gcount()); return buffer; } // 发送.gz文件的正确实现 void send_gzip_file(tcp::socket& socket, const std::string& filename, std::size_t total_file_size) { std::ifstream file(filename, std::ios::binary); if (!file.is_open()) { std::cerr << "Failed to open file: " << filename << std::endl; // 返回404响应 http::response<http::empty_body> res; res.result(http::status::not_found); res.prepare_payload(); http::write(socket, res); return; } // 构造一次响应头 http::response<http::empty_body> res; res.result(http::status::ok); // 设置gz文件对应的标准Content-Type res.set(http::field::content_type, "application/gzip"); // 仅设置一次总文件大小 res.set(http::field::content_length, std::to_string(total_file_size)); // 发送响应头 http::write(socket, res); // 分块发送文件内容 std::vector<char> chunk; do { chunk = readChunk(file, chunk_size); if (!chunk.empty()) { // 直接发送二进制数据,无需包装成response boost::asio::write(socket, boost::asio::buffer(chunk.data(), chunk.size())); } } while (!chunk.empty()); } // 主调用代码修正 try{ std::ifstream file("tcpdump.pcap.gz", std::ios::binary | std::ios::ate); if (!file.is_open()) { std::cerr << "Failed to open file: tcpdump.pcap.gz" << std::endl; return; } std::size_t file_size = file.tellg(); file.close(); // 此处关闭,send_gzip_file会重新打开文件 std::cout << "size of file is " << file_size << std::endl; // 调用修正后的发送函数 send_gzip_file(socket, "tcpdump.pcap.gz", file_size); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; }
方式二:使用HTTP分块传输编码(无需提前知道文件大小)
如果不想提前计算文件大小,可以用HTTP的分块传输编码,此时不需要设置Content-Length,而是设置Transfer-Encoding: chunked,每次发送块的十六进制长度+内容,最后发送0长度块结束。
示例代码核心部分:
// 构造分块响应头 http::response<http::empty_body> res; res.result(http::status::ok); res.set(http::field::content_type, "application/gzip"); res.set(http::field::transfer_encoding, "chunked"); res.prepare_payload(); http::write(socket, res); // 分块发送内容 std::vector<char> chunk; do { chunk = readChunk(file, chunk_size); if (!chunk.empty()) { // 发送块长度(十六进制格式) std::ostringstream oss; oss << std::hex << chunk.size() << "\r\n"; boost::asio::write(socket, boost::asio::buffer(oss.str())); // 发送块内容 boost::asio::write(socket, boost::asio::buffer(chunk.data(), chunk.size())); // 发送块结束标记 boost::asio::write(socket, boost::asio::buffer("\r\n")); } } while (!chunk.empty()); // 发送分块结束标记 boost::asio::write(socket, boost::asio::buffer("0\r\n\r\n"));
疑问解答
- 服务器不需要解压.gz文件,直接发送原始二进制内容即可,客户端保存接收到的二进制数据就是完整的.gz文件。
- 绝对不要解压后再发送,这会浪费带宽,且会让客户端额外执行压缩操作,完全不符合你的需求。
内容的提问来源于stack exchange,提问作者Niranjan Upadhya
相关产品推荐
相关产品推荐

