You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自制C++ HTTP服务器无法正常提供PNG等二进制文件服务

问题分析与修复方案

你的HTTP服务器处理二进制文件时出现传输异常,核心问题集中在字符串长度计算错误、HTTP头格式错误、发送逻辑不严谨这几点,以下是具体问题和修复代码:

核心问题拆解

  1. sendContents函数的致命错误:用strlen(contents.c_str())计算发送长度,二进制文件内容中必然包含\0字符,strlen会在此处截断,导致实际发送的数据远小于预期(这也是第一种方法仅能传6KB的原因)。
  2. HTTP头格式错误:Content-Length字段缺少冒号,浏览器无法正确解析内容长度,引发传输大小与文件大小不匹配的问题。
  3. 文件大小获取路径错误:使用未安全解析的path调用file_size,而非经过canonical处理后的actualPath,可能因路径解析问题导致大小获取不准确。
  4. 逐字节发送低效且不可靠:逐字节调用send不仅效率极低,且未处理send可能返回部分发送的情况,存在数据丢失风险。
  5. 缓冲区初始化多余且错误:*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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 07:55:17