基于boost::asio的文件传输问题:文件大小读取错误致循环无法终止
问题描述
使用boost::asio库从NFOServer向PC传输文件时遇到两个核心问题:
- 客户端读取到的文件大小与服务器实际文件大小不符:服务器显示文件大小为2975744字节(约2.9MB),但客户端读取到的文件大小为12894362189字节;
- 服务器发送完正确大小的文件后,客户端未触发EOF标志,接收循环无法终止。
客户端代码
void receive_file_content_over_tcp(const std::string& filePath, boost::asio::ip::tcp::socket& socket) { std::ofstream file(filePath, std::ios::binary | std::ios::trunc); if (!file.is_open()) { std::cerr << "Failed to open file for receiving." << std::endl; return; } boost::asio::streambuf receive_buffer; boost::asio::streambuf fileSizeBuffer; boost::system::error_code error; std::size_t totalBytesReceived = 0; std::size_t fileSize = 0; // Receive file size boost::asio::read(socket, fileSizeBuffer, boost::asio::transfer_exactly(sizeof(std::size_t)), error); if (error) { std::cerr << "Failed to receive file size: " << error.message() << std::endl; return; } // Extract file size from the buffer std::istream fileSizeStream(&fileSizeBuffer); fileSizeStream.read(reinterpret_cast<char*>(&fileSize), sizeof(std::size_t)); std::cout << "Receiving file. File size: " << fileSize << " bytes." << std::endl; // Receive file content while (totalBytesReceived < fileSize) { std::size_t bytesRead = boost::asio::read(socket, receive_buffer, boost::asio::transfer_at_least(1), error); if (error && error != boost::asio::error::eof) { std::cerr << "Error while receiving file content: " << error.message() << std::endl; return; } if (bytesRead > 0) { // Write the received data to the file totalBytesReceived += bytesRead; std::istream is(&receive_buffer); file << is.rdbuf(); // Display progress std::cout << "Received: " << totalBytesReceived << " bytes out of " << fileSize << " bytes (" << (totalBytesReceived * 100 / fileSize) << "%)." << std::endl; } } std::cout << "File content received successfully. Total bytes received: " << totalBytesReceived << std::endl; }
服务器端代码
void send_file_content_over_tcp(const std::string& filePath, boost::asio::ip::tcp::socket& socket) { std::ifstream file(filePath, std::ios::binary | std::ios::ate); if (!file.is_open()) { std::cerr << "Failed to open file for sending." << std::endl; return; } // Get the file size std::size_t fileSize = file.tellg(); file.seekg(0, std::ios::beg); // Create a buffer to hold the file content std::vector<char> buffer(fileSize); if (!file.read(buffer.data(), fileSize)) { std::cerr << "Failed to read file for sending." << std::endl; return; } std::cout << "File read successfully. File size: " << fileSize << " bytes." << std::endl; // Send the file content boost::system::error_code error; std::size_t bytesSent = boost::asio::write(socket, boost::asio::buffer(buffer), error); if (error) { std::cerr << "Error while sending file content: " << error.message() << std::endl; } else { std::cout << "File content sent successfully. Bytes sent: " << bytesSent << std::endl; } }
客户端输出
Receiving file. File size: 12894362189 bytes. Received: 512 bytes out of 12894362189 bytes (0%).
服务器端输出
File read successfully. File size: 2975744 bytes. File content sent successfully. Bytes sent: 2975744
问题分析与解决方案
核心问题1:文件大小读取错误
服务器代码完全没有发送文件大小,但客户端却在尝试读取sizeof(std::size_t)字节作为文件大小,导致客户端读取到的是文件内容的前8字节(64位系统下),这就是文件大小显示异常巨大的直接原因。
核心问题2:接收循环无法终止
客户端错误认为需要接收超大量数据,而服务器仅发送了实际文件内容,之后未关闭连接写端,客户端会一直等待更多数据,无法触发EOF标志,循环无法终止。
修正方案
服务器端修改:先发送文件大小,分块发送文件内容
void send_file_content_over_tcp(const std::string& filePath, boost::asio::ip::tcp::socket& socket) { std::ifstream file(filePath, std::ios::binary | std::ios::ate); if (!file.is_open()) { std::cerr << "Failed to open file for sending." << std::endl; return; } // 获取文件大小 std::size_t fileSize = file.tellg(); file.seekg(0, std::ios::beg); std::cout << "File read successfully. File size: " << fileSize << " bytes." << std::endl; boost::system::error_code error; // 先发送文件大小 boost::asio::write(socket, boost::asio::buffer(&fileSize, sizeof(std::size_t)), error); if (error) { std::cerr << "Error while sending file size: " << error.message() << std::endl; return; } // 分块发送文件内容,避免大文件占用过多内存 std::vector<char> buffer(8192); std::size_t totalBytesSent = 0; while (file.read(buffer.data(), buffer.size()) || file.gcount() > 0) { std::size_t bytesToSend = file.gcount(); boost::asio::write(socket, boost::asio::buffer(buffer.data(), bytesToSend), error); if (error) { std::cerr << "Error while sending file content: " << error.message() << std::endl; return; } totalBytesSent += bytesToSend; } std::cout << "File content sent successfully. Bytes sent: " << totalBytesSent << std::endl; // 关闭socket写端,触发客户端EOF socket.shutdown(boost::asio::ip::tcp::socket::shutdown_send, error); }
客户端修改:优化接收逻辑,避免缓冲区残留问题
void receive_file_content_over_tcp(const std::string& filePath, boost::asio::ip::tcp::socket& socket) { std::ofstream file(filePath, std::ios::binary | std::ios::trunc); if (!file.is_open()) { std::cerr << "Failed to open file for receiving." << std::endl; return; } boost::system::error_code error; std::size_t totalBytesReceived = 0; std::size_t fileSize = 0; // 接收文件大小 boost::asio::read(socket, boost::asio::buffer(&fileSize, sizeof(std::size_t)), error); if (error) { std::cerr << "Failed to receive file size: " << error.message() << std::endl; return; } std::cout << "Receiving file. File size: " << fileSize << " bytes." << std::endl; // 分块接收文件内容,每次最多接收8KB std::vector<char> buffer(8192); while (totalBytesReceived < fileSize) { std::size_t bytesToRead = std::min(buffer.size(), fileSize - totalBytesReceived); std::size_t bytesRead = boost::asio::read(socket, boost::asio::buffer(buffer, bytesToRead), error); if (error) { if (error == boost::asio::error::eof) { std::cerr << "Connection closed before receiving full file." << std::endl; } else { std::cerr << "Error while receiving file content: " << error.message() << std::endl; } return; } file.write(buffer.data(), bytesRead); totalBytesReceived += bytesRead; // 显示进度 std::cout << "Received: " << totalBytesReceived << " bytes out of " << fileSize << " bytes (" << static_cast<double>(totalBytesReceived) / fileSize * 100 << "%)." << std::endl; } std::cout << "File content received successfully. Total bytes received: " << totalBytesReceived << std::endl; }
额外说明
- 原服务器代码一次性将整个文件读入内存,大文件场景下会造成内存压力,修改后改为分块读取发送;
- 客户端原代码使用
streambuf未清理,易引发数据残留问题,改为固定大小vector缓冲区更可靠; - 服务器发送完成后调用
shutdown关闭写端,确保客户端能触发EOF,避免无限等待。
内容的提问来源于stack exchange,提问作者Johannes W.
相关产品推荐
相关产品推荐

