在Rust中检测EOF而不读取0字节

yzxexxkh  于 2023-04-30  发布在  其他
关注(0)|答案(2)|浏览(139)

我一直在编写一些代码,从Read类型(input)中读取数据,并对每个块进行一些处理。问题是最后一个块需要用不同的函数来处理。据我所知,有几种方法可以从Read中检测EOF,但在这种情况下没有一种感觉特别符合人体工程学。我在找一个更地道的解决方案。
我目前的方法是维护两个缓冲区,这样如果下一个读取读取零字节,则可以维护上一个读取结果,在这种情况下表示EOF,因为缓冲区的长度为非零:

use std::io::{Read, Result};

const BUF_SIZE: usize = 0x1000;

fn process_stream<I: Read>(mut input: I) -> Result<()> {
    // Stores a chunk of input to be processed
    let mut buf = [0; BUF_SIZE];
    let mut prev_buf = [0; BUF_SIZE];
    let mut prev_read = input.read(&mut prev_buf)?;

    loop {
        let bytes_read = input.read(&mut buf)?;
        if bytes_read == 0 {
            break;
        }

        // Some function which processes the contents of a chunk
        process_chunk(&prev_buf[..prev_read]);

        prev_read = bytes_read;
        prev_buf.copy_from_slice(&buf[..]);
    }

    // Some function used to process the final chunk differently from all other messages
    process_final_chunk(&prev_buf[..prev_read]);
    Ok(())
}

我觉得这是一种非常丑陋的方式,我不应该在这里使用两个缓冲区。
我能想到的一个替代方案是在input上强加Seek,并使用input.read_exact()。然后,我可以检查UnexpectedEof错误类型,以确定我们已经到达了输入的末尾,并向后查找以再次读取最后一个块(这里需要再次查找和读取,因为在UnexpectedEof错误的情况下,缓冲区的内容是未定义的)。但这似乎一点也不符合习惯用法:遇到一个错误,寻找回来,并再次阅读只是为了检测我们在一个文件的末尾是非常奇怪的。
我理想的解决方案是这样的,使用一个假想的input.feof()函数,如果最后一个input.read()调用到达EOF,则返回true,如feof syscall in C

fn process_stream<I: Read>(mut input: I) -> Result<()> {
    // Stores a chunk of input to be processed
    let mut buf = [0; BUF_SIZE];
    let mut bytes_read = 0;

    loop {
        bytes_read = input.read(&mut buf)?;

        if input.feof() {
            break;
        }

        process_chunk(&buf[..bytes_read]);
    }

    process_final_chunk(&buf[..bytes_read]);
    Ok(())
}

谁能提出一种更习惯的实现方法?谢谢!

btqmn9zl

btqmn9zl1#

std::io::Readread返回Ok(n)时,这不仅意味着缓冲区buf已经填充了来自此源的n字节的数据。,但它也指示索引n(包括n)之后的字节保持不变。考虑到这一点,实际上根本不需要prev_buf,因为当n为0时,缓冲区的所有字节都将保持不动(使它们成为前一次读取的字节)。
prog-fh的解决方案是您想要的处理类型,因为它只会将完整的块传递给process_chunk。由于read可能返回一个介于0BUF_SIZE之间的值,因此需要这样做。有关更多信息,请参阅上述链接的这一部分:
如果返回的值n小于缓冲区大小,这不是错误,即使读取器还没有到达流的末尾。这可能发生,例如,因为现在实际可用的字节较少(例如:例如,接近文件结束)或因为read()被信号中断。
但是,我建议您考虑一下,当您从read中获得一个不代表永远结束文件的Ok(0)时,应该发生什么。请参阅本部分:
如果n为0,则它可以指示两种情况之一:
1.此读取器已到达其“文件结尾”,可能无法再生成字节。请注意,这并不意味着读取器将永远无法再产生字节。
因此,如果您要获得一个返回Ok(BUF_SIZE), Ok(BUF_SIZE), 0, Ok(BUF_SIZE)的读取序列(这是完全可能的,它只是表示IO中的一个故障),您是否不希望将最后一个Ok(BUF_SIZE)视为读取块?如果你把Ok(0)永远当作EOF,那可能是一个错误。
可靠地确定什么应该被认为是最后一个块的唯一方法是将预期的长度(以字节为单位,而不是块的数量)作为协议的一部分预先发送。给定一个变量expected_len,然后可以通过expected_len - expected_len % BUF_SIZE确定最后一个块的开始索引,而expected_len本身的结束索引。

8ehkhllq

8ehkhllq2#

既然你认为read_exact()是一个可能的解决方案,那么我们可以认为一个非final块正好包含BUF_SIZE字节。那么,为什么不尽可能多地读取数据来填充这样一个缓冲区,然后用一个函数处理它,然后,当绝对不可能时(因为达到EOF),用另一个函数处理不完整的最后一个块呢?
注意,C中的feof()不会 * 猜测 * 在下次读取尝试时会达到EOF;它仅报告在先前读取尝试期间可能已经设置的EOF标志。因此,要设置EOF并报告它,必须首先遇到返回0的读取尝试(如下面的示例所示)。

编辑

感谢@AndreKR的评论,算法不考虑大小为零的最终块似乎很重要。即当文件中的字节总量是预期块大小的精确倍数时)。因此,我调整了算法(最近,对不起。..),结果是额外的复杂性:我们必须在处理每个完整的块之前保存它,只是为了检查下一个是否为空,然后决定哪个块可以被认为是最终的。
请注意,我选择在两个不同的缓冲区之间交替,并让字节就位,而不是将字节从一个容器复制到另一个容器。

use std::fs::File;
use std::io::{Read, Result};

const BUF_SIZE: usize = 0x1000;

fn process_chunk(bytes: &[u8]) {
    println!(
        "process_chunk {} ~~> {:?}...",
        bytes.len(),
        &bytes[..5.min(bytes.len())]
    );
}
fn process_final_chunk(bytes: &[u8]) {
    println!(
        "process_final_chunk {} ~~> {:?}...",
        bytes.len(),
        &bytes[..5.min(bytes.len())]
    );
}

fn process_stream<I: Read>(mut input: I) -> Result<()> {
    // stores two chunks of input in order to postpone
    // their process as final or not
    let mut buffer = [[0; BUF_SIZE]; 2];
    let mut buffer_bytes = [0; 2];
    let mut buffer_idx = 0;
    loop {
        let mut bytes_read = 0;
        let mut eof = false;
        // read a chunk until completion or EOF
        while bytes_read < BUF_SIZE {
            let r = input.read(&mut buffer[buffer_idx][bytes_read..])?;
            if r == 0 {
                eof = true;
                break;
            }
            bytes_read += r;
        }
        buffer_bytes[buffer_idx] = bytes_read;
        if bytes_read != 0 {
            // the previous chunk (if any) was not the final one
            if buffer_bytes[1 - buffer_idx] != 0 {
                process_chunk(&buffer[1 - buffer_idx]);
            }
        }
        if eof {
            if bytes_read == 0 {
                // the previous chunk (if any) was the final one
                if buffer_bytes[1 - buffer_idx] != 0 {
                    process_final_chunk(&buffer[1 - buffer_idx]);
                }
            } else {
                // the (incomplete) current chunk is the final one
                process_final_chunk(&buffer[buffer_idx][..bytes_read]);
            }
            break;
        }
        buffer_idx = 1 - buffer_idx;
    }
    Ok(())
}

fn main() {
    let file = File::open("data.bin").unwrap();
    process_stream(file).unwrap();
}
/*
$ dd if=/dev/random of=data.bin bs=1024 count=9
$ cargo run
process_chunk 4096 ~~> [31, 172, 180, 87, 119]...
process_chunk 4096 ~~> [159, 73, 228, 79, 142]...
process_final_chunk 1024 ~~> [153, 213, 2, 223, 17]...
$ truncate -s 8192 data.bin
$ cargo run
process_chunk 4096 ~~> [31, 172, 180, 87, 119]...
process_final_chunk 4096 ~~> [159, 73, 228, 79, 142]...
*/

相关问题