Rust 创建读取命名管道
Linux 操作系统进程间的通讯方式有: signal/socket/fifo
我要实现一个功能就是后台守护进程运行的时候,能够通过命令行执行一些简单的控制,因为有时候需要发送一些数据给守护进程,所以没有选择信号量控制守护进程,刚刚开始写代码的时候选择使用的是 socket。代码写完之后才发现,不能直接通过 shell 数据重定向的方式写入:
echo "hello socket!" > /tmp/my_socket.sock
这么操作 socket 是错误,只能通过其他方式读写。而且 socket 是双向通讯,然后我就切换到了命名管道。命名管道是单向通讯的,也是符合我的需求。
主要的流程为:
- 创建命名管道
- 守护程序已打开普通文件的方式持续读取数据
- shell 端直接写入数据
- 完成控制逻辑
rust 端要创建一个命名管道需要调用 libc 的方法,传递的字符串还需要转换成 CString 类型。调用 mkfifo 创建一个命名管道,还需要使用 unsafe 包裹起来,第二个参数是设定管道的写入权限,不知道为什么设定了好像不太对的样子,后来我创建完管道之后写入数据之前,直接使用 chmod 改成需要的权限,这样子普通用户也可以写入数据。
创建管道的操作:
let path = CString::new("/tmp/named_pipe_o1").unwrap();
if unsafe { libc::mkfifo(path.as_ptr(), 0o666) } != 0 {
let e = Error::last_os_error();
// error handle here
}
读取数据的操作:
use std::io::{BufRead, BufReader};
let path = Path::new("/tmp/named_pipe_o1");
let f = std::fs::File::open(path).unwrap();
let mut reader = std::io::BufReader::new(f);
for line in reader.lines() {
let Ok(txt) = line else { break; };
// do something here...
}
如果使用上面的代码会发现,每完成一次数据读取之后,就会到达 break。在写这代码之前,我还用过 tokio 写的异步版本代码,代码需要实现持续监听管道内容:
// 打开文件并创建读取器
let file = File::open(&fifo_path).await?;
let mut reader = BufReader::new(file).lines();
// 持续读取文件内容
loop {
if let Some(line) = reader.next_line().await? {
println!("{}", line);
}
}
之后在 shell 端往管道里面写入数据,会发现打印发送的数据之后,管道就会一直读取到空数据,并且阻塞消失了。 这是因为每完成一次数据交互之后,管道就会关闭,读取端就会连续不断得读取到 EOF,导致程序感觉像是阻塞消失了,但如果再往管道里面发送数据,还是可以读取到。 即读取到的数据可能为:
data...
data...
data_ed
EOF
EOF
EOF
...
data_2...
data_2...
data_2_end
EOF
EOF
EOF
...
后面我把 loop 放到外面,即每次循环都是重新打开管道,读取数据之后就会关闭管道:
loop {
let f = std::fs::File::open(path).unwrap();
let mut reader = std::io::BufReader::new(f);
for line in reader.lines() {
let Ok(txt) = line else { break; };
// do something here...
}
}
这样,代码就会正常工作了。