Linux 操作系统进程间的通讯方式有: signal/socket/fifo

我要实现一个功能就是后台守护进程运行的时候,能够通过命令行执行一些简单的控制,因为有时候需要发送一些数据给守护进程,所以没有选择信号量控制守护进程,刚刚开始写代码的时候选择使用的是 socket。代码写完之后才发现,不能直接通过 shell 数据重定向的方式写入:

echo "hello socket!" > /tmp/my_socket.sock

这么操作 socket 是错误,只能通过其他方式读写。而且 socket 是双向通讯,然后我就切换到了命名管道。命名管道是单向通讯的,也是符合我的需求。

主要的流程为:

  1. 创建命名管道
  2. 守护程序已打开普通文件的方式持续读取数据
  3. shell 端直接写入数据
  4. 完成控制逻辑

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...
    }
}

这样,代码就会正常工作了。