在 C++ 中流式传输二进制文件

Streaming binary files in C++

来自 C,我正在尝试使用 C++,并且偶然发现了一些简单的事情,比如使用 ifstream 从文件中读取二进制数据到缓冲区。在我看来,我有三个选项可以从文件中读取数据:

令我感到特别奇怪的是 read() 函数,在我看来它完全无法使用,因为它无法说明它实际放入提供的缓冲区中的字节数。然而,我看到的所有使用它的示例代码似乎都证实了这一点,并且通常会寻找文件的末尾以获取文件的大小,然后分配缓冲区。显然,这不适用于流数据。

那么实际上应该如何在 C++ 中流式传输带有非文本数据的 file/pipe/socket?有没有比 ifstream 更好的设施?

"What strikes me as particularly weird is the read() function, which seems to me to be completely unusable seeing as how it doesn't tell how many bytes it actually put into the supplied buffer."

好吧,您指定一次最多可以读取多少字节,然后可以使用 std::ifstream::eof() 检查状态来检查流是否已经耗尽。

如果您想知道文件中已经有多少字节可用,并相应地分配您的读取缓冲区,您可以事先检查整个文件大小,如前所述,对 the reference 中的示例代码稍作修改:

// read entire file into string
std::ifstream is("test.txt", std::ifstream::binary);
if (is) {
    // get length of file:
    is.seekg(0, is.end);
    int length = is.tellg();
    is.seekg(0, is.beg);

    std::string str;
    str.resize(length, ' '); // ******* reserve space ********
    char* begin = &*str.begin();

    is.read(begin, length);
    is.close();

    std::cout << str << "\n";
} else {
    std::cout << "Could not open test.txt\n";
}

请注意,你的问题的关键点是,如果你能够一次完全分配 length,或者你是否必须拆分读取较小的块并在你不这样做时串行解析这些块能够提供能够容纳完整文件大小的缓冲区。

"Clearly, this doesn't work for streaming data, however."

这只是将接收到的数据块从异步任务解耦到更高级别的线程以 流式处理 方式使用数据的问题。

What strikes me as particularly weird is the read() function, which seems to me to be completely unusable seeing as how it doesn't tell how many bytes it actually put into the supplied buffer.

read() 不会退出,直到 1) 请求的字符数已被读取,2) 达到 EOF,或 3) 发生错误。

read()退出后,如果读取成功,可以调用gcount()查看缓冲区中读入了多少个字符。如果在读取期间达到 EOF,流的 eofbit 状态将设置为 true,并且 gcount() 将 return 比您请求的字符少。

如果读取失败,流的 failbit and/or badbit 状态设置为真。

std::ifstream ifs(...);
if (is) {
    // stream opened...
    //...
    ifs.read(buffer, sizeof(buffer));
    if (ifs) {
        // read succeeded, EOF may have been reached...
        std::streamsize numInBuf = ifs.gcount();
        //...
    } else {
        // read failed...
    }
    //...
} else {
    // stream not opened...
}

如果您使用流的 exceptions() 方法通过异常启用错误报告,如果故障与您为其启用异常的错误位匹配,则可能会抛出 std::ios_base::failure 异常。

std::ifstream ifs;
ifs.exceptions(std::ifstream::badbit | std::ifstream::failbit);
try {
    ifs.open(...);
    // stream opened...
    //...
    ifs.read(buffer, sizeof(buffer));
    // read succeeded, EOF may have been reached...
    std::streamsize numInBuf = ifs.gcount();
    //...
} catch (const std::ios_base::failure &e) {
    // stream failure...
}

So how is one actually supposed to stream a file/pipe/socket with non-text data in C++? Is there some better facility than ifstream, perhaps?

std::ifstream 专为基于文件的流而设计。对于管道,如果您的平台可以通过标准文件 API 访问管道,std::ifstream 应该可以。但是对于套接字,您需要使用更合适的 std::basic_istream 派生 class,或者至少使用标准 std::istream 和附加的自定义 std::streambuf 派生 class它 (example).