自制C++ HTTP服务器无法正常提供PNG等二进制文件服务
问题分析与修复方案
你的HTTP服务器处理二进制文件时出现传输异常,核心问题集中在字符串长度计算错误、HTTP头格式错误、发送逻辑不严谨这几点,以下是具体问题和修复代码:
核心问题拆解
sendContents函数的致命错误:用strlen(contents.c_str())计算发送长度,二进制文件内容中必然包含\0字符,strlen会在此处截断,导致实际发送的数据远小于预期(这也是第一种方法仅能传6KB的原因)。- HTTP头格式错误:
Content-Length字段缺少冒号,浏览器无法正确解析内容长度,引发传输大小与文件大小不匹配的问题。 - 文件大小获取路径错误:使用未安全解析的
path调用file_size,而非经过canonical处理后的actualPath,可能因路径解析问题导致大小获取不准确。 - 逐字节发送低效且不可靠:逐字节调用
send不仅效率极低,且未处理send可能返回部分发送的情况,存在数据丢失风险。 - 缓冲区初始化多余且错误:
*buffer = {0};仅清空了第一个字节,其余缓冲区内容未初始化,虽不影响文件读取,但属于无效操作。
修复后的代码
首先替换发送逻辑,确保完整发送数据
// 通用发送函数:确保所有数据都发送完毕,支持二进制数据 ssize_t WebServer::sendAll(const char* data, size_t len) { size_t sent = 0; while (sent < len) { ssize_t ret = send(this->newFd, data + sent, len - sent, 0); if (ret == -1) { throw std::runtime_error("Send failed: " + std::string(strerror(errno))); } sent += ret; } return sent; } // 封装发送字符串的便捷函数 void WebServer::sendContents(const std::string& contents) { sendAll(contents.c_str(), contents.size()); }
修复后的sendFile函数
void WebServer::sendFile(std::string path) { path = "./" + path; std::string mimeType; std::string::size_type idx = path.rfind('.'); if (idx != std::string::npos) { mimeType = this->getMimeType(path.substr(idx + 1)); } else { mimeType = "text/html"; } std::filesystem::path reqPath = std::filesystem::path(path).make_preferred(); std::filesystem::path parentPath = std::filesystem::path("./"); std::filesystem::path actualPath; try { actualPath = std::filesystem::canonical(parentPath / reqPath); } catch (const std::filesystem::filesystem_error& e) { this->sendRoute("404"); return; } if (!this->isSubDir(actualPath, parentPath)) { this->sendRoute("404"); return; } std::ifstream ifs(actualPath, std::ios::binary); if (!ifs.is_open()) { sendContents("HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n"); return; } std::size_t length = std::filesystem::file_size(actualPath); std::unique_ptr<char[]> buffer(new char[length]); // 智能指针自动管理内存,避免泄漏 ifs.read(buffer.get(), length); // 检查文件是否完整读取 if (!ifs.eof() || ifs.gcount() != length) { std::cerr << "Error! Only " << ifs.gcount() << " bytes read, expected " << length << std::endl; sendContents("HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n"); return; } // 修复Content-Length的冒号,修正HTTP头格式 std::string resp = "HTTP/1.0 200 OK\r\n" "Server: webserver-c\r\n" "Content-Length: " + std::to_string(length) + "\r\n" "Content-Type: " + mimeType + "\r\n\r\n"; sendContents(resp); sendAll(buffer.get(), length); // 批量发送文件内容,高效且可靠 ifs.close(); }
关键修复说明
sendAll函数:解决了二进制数据含\0的长度计算问题,同时循环发送确保所有数据都能传输到客户端,避免send返回部分发送导致的数据丢失。- HTTP头修正:为
Content-Length添加冒号,让浏览器正确识别内容长度,解决传输大小与文件大小不匹配的问题。 - 路径与文件大小:使用安全解析后的
actualPath获取文件大小,避免路径解析错误导致的大小偏差。 - 内存安全:用
std::unique_ptr管理缓冲区,自动释放内存,避免手动delete可能引发的泄漏。 - 读取完整性检查:验证实际读取的字节数是否等于文件大小,提前发现读取错误并返回500错误。
内容的提问来源于stack exchange,提问作者bsdsylvia
相关产品推荐
相关产品推荐

