如何在stdin上实现块迭代器?



我需要实现一个通过stdin接收消息的长时间运行的程序。协议将消息定义为长度指示符(为简单起见,为1字节整数)的形式,然后是长度指示符表示的长度字符串。消息之间没有空格。程序应该消耗来自stdin的所有消息,并等待另一个消息。

如何在stdin上实现这样的等待?

我以一种方式实现了迭代器,它尝试从stdin中读取并在错误情况下重复。它是有效的,但效率很低。我希望迭代器在有新数据时读取消息

我的实现是使用read_exact:

use std::io::{Read, stdin, Error as IOError, ErrorKind};
pub struct In<R>(R) where R: Read;
pub trait InStream{
fn read_one(&mut self) -> Result<String, IOError>;
}
impl <R>In<R> where R: Read{
pub fn new(stdin: R) -> In<R> {
In(stdin)
}
}
impl <R>InStream for In<R> where R: Read{
/// Read one message from stdin and return it as string
fn read_one(&mut self) -> Result<String, IOError>{
const length_indicator: usize = 1;
let stdin = &mut self.0;
let mut size: [u8;length_indicator] = [0; length_indicator];
stdin.read_exact(&mut size)?;
let size = u8::from_be_bytes(size) as usize;
let mut buffer = vec![0u8; size];
let _bytes_read = stdin.read_exact(&mut buffer);
String::from_utf8(buffer).map_err(|_| IOError::new(ErrorKind::InvalidData, "not utf8"))
}
}
impl <R>Iterator for In<R> where R:Read{
type Item = String;
fn next(&mut self) -> Option<String>{
self.read_one()
.ok()
}
}
fn main(){
let mut in_stream = In::new(stdin());
loop{
match in_stream.next(){
Some(x) => println!("x: {:?}", x),
None => (),
}
}
}

我通过Read和BufReader文档,但没有方法似乎可以解决我的问题,因为read文档包含以下文本:

这个函数不提供任何关于它是否阻塞等待数据的保证,但是如果一个对象需要阻塞读取而不能阻塞,它通常会通过Err返回值发出信号。

如何在stdin上实现等待数据?

= = =

编辑:最小的用例,不阻塞和循环给出的unexpected deof错误,而不是等待数据:

use std::io::{Read, stdin};
fn main(){
let mut stdin = stdin();
let mut stdin_handle = stdin.lock();
loop{
let mut buffer = vec![0u8; 4];
let res = stdin_handle.read_exact(&mut buffer);
println!("res: {:?}", res);
println!("buffer: {:?}", buffer);
}

我通过cargo run < in在OSX上运行它,其中in被命名为管道。我用echo -n "1234" > in填充管道。

等待第一个输入,然后循环。

res: Ok(())
buffer: [49, 50, 51, 52]
res: Err(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" })
buffer: [0, 0, 0, 0]
res: Err(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" })
buffer: [0, 0, 0, 0]
res: Err(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" })
buffer: [0, 0, 0, 0]
res: Err(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" })
buffer: [0, 0, 0, 0]
res: Err(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" })
...

我想让程序等待,直到有足够的数据填充缓冲区。

正如其他人所解释的,Read上的文档是非常通用的,并不适用于标准输入,阻塞的。换句话说,添加了缓冲的代码很好。

问题是你如何使用管道。例如,如果在一个shell中运行mkfifo foo; cat <foo,在另一个shell中运行echo -n bla >foo,您将看到第一个shell中的cat将显示foo并退出。关闭管道的最后一个写入器会向读取器发送EOF,从而使程序的stdin失效。

您可以通过在后台启动另一个程序来解决这个问题,该程序以写模式打开管道并且永远不会退出,例如tail -f /dev/null >pipe-filename。然后,您的程序将观察到echo -n bla >foo,但不会导致其stdin关闭。"holding">