我有两个不同的 C++ 程序,它们通过(linux)管道相互通信。
一个进程是master,另一个进程是slave。它们中的任何一个都可能崩溃或简单地重新启动,但绝不会同时发生(让我们假设这一点)。
Slave 以阻塞方式以写入模式创建并打开管道 A(与最后一个特征无关),然后以阻塞方式以读取模式创建并打开管道 B(同样,与最后一个特征无关)。
Master 以非阻塞方式在读取模式下创建并打开管道 A(同样,与最后一个功能无关),然后等待一段时间,以便 slave 可以退出管道 A 的阻塞创建和管道 B 的创建阻塞,然后创建和也以非阻塞方式以写入模式打开管道 B(同样,不相关的最后一个功能)。
int open_pipe_read_mode(const char *file_path, bool dont_block)
{
int pipe_fd = -1;
if ((mkfifo(file_path, 0666) != 0) && (errno != EEXIST)) {
return pipe_fd;
}
if (dont_block) {
pipe_fd = open(file_path, O_RDONLY | O_NONBLOCK); // For write mode: O_WRONLY | O_CREAT | O_NONBLOCK
} else {
pipe_fd = open(file_path, O_RDONLY); // For write mode: O_WRONLY | O_CREAT
}
return pipe_fd;
}
只要两端都启动并运行,到目前为止一切顺利。从我的代码中可以看出,实际上只有一个进程会使用 mkfifo create 管道,而另一个进程将因 EEXIST 而失败。
当两个进程之一重新启动时,我的问题就出现了。如果其他进程在停机期间不使用管道,重新启动的进程是否会在调用我的函数
open_pipe_read_mode
或 open_pipe_write_mode
时崩溃/失败?或者,由于这些管道是使用 mkfifo 作为文件创建的,因此可以像这样重新打开一个吗?请注意,在我的代码中,我考虑(忽略)来自 mkfifo 的 EEXIST 错误。
我一直在阅读其他问题,例如这个,但我发现的只是“你不能”或“你为什么要这样做?只是不要”。我相信创建没有文件名的管道确实不可能(即麻烦且不安全,绝对不推荐)按照我的意图重新打开它们。
是否有任何反对此的建议(即,如果它仅适用于浏览未定义的行为效果但它不能在任何随机点工作)?
我的实际问题是:考虑到已经使用 mkfifo 创建了一个文件名的管道,并且其两侧已被两个不同的进程打开,是否可以打开管道的一端(无论是读还是写,阻塞或不)在该端先前已关闭且另一端保持打开状态之后?
我的代码在重新启动任一进程之前都按照描述工作,但我无法设法测试重新启动场景,因为我的项目很复杂。在开发几个简单的程序之前(主要是因为使用它们曾经对确定性毫无意义,撇开良好实践不谈)我决定在互联网上查找,我很惊讶地发现没有问题/答案。
两个非常基本的程序 master 和 slave 打开两个管道 A 和 B 来交换信息。管道A是从主到主的流,管道B是主到从的流。交换信息无关紧要,这里只是检查SIGPIPE等错误。
int master_main()
{
// Open pipe A as read (non-blocking)
pid_t pid = getpid();
int read_pipe_fd = open_pipe_read_mode("./test_pipe_A", true);
if (read_pipe_fd < 0) {
printf("%d\tError open_pipe_read_mode\n", pid);
return 1;
}
sleep(1); // Wait for slave program to open pipe B
// Open pipe B as write (non-blocking)
int write_pipe_fd = open_pipe_write_mode("./test_pipe_B", true);
if (write_pipe_fd < 0) {
printf("%d\tError open_pipe_write_mode\n", pid);
return 1;
}
int data_var = 1234;
do {
// Write data
if (write(write_pipe_fd, (int*)&data_var, sizeof(int)) < 0) {
printf("%d\tError write\n", pid);
} else {
printf("%d\tWrote data_var = %d\n", pid, data_var);
}
// Read data
ssize_t count = read(read_pipe_fd, &data_var, sizeof(int));
if (count != sizeof(int)) {
printf("%d\tError read\n", pid);
} else {
printf("%d\tRead data_var = %d\n", pid, data_var);
}
data_var++;
sleep(10);
} while (true);
}
int slave_main()
{
// Open pipe A as write (blocking)
pid_t pid = getpid();
int write_pipe_fd = open_pipe_write_mode("./test_pipe_A", false);
if (write_pipe_fd < 0) {
printf("%d\tError open_pipe_write_mode\n", pid);
return 1;
}
// Open pipe B as read (blocking)
int read_pipe_fd = open_pipe_read_mode("./test_pipe_B", false);
if (read_pipe_fd < 0) {
printf("%d\tError open_pipe_read_mode\n", pid);
return 1;
}
int data_var = 5678;
do {
// Write data
if (write(write_pipe_fd, (int*)&data_var, sizeof(int)) < 0) {
printf("%d\tError write\n", pid);
} else {
printf("%d\tWrote data_var = %d\n", pid, data_var);
}
// Read data
ssize_t count = read(read_pipe_fd, &data_var, sizeof(int));
if (count != sizeof(int)) {
printf("%d\tError read\n", pid);
} else {
printf("%d\tRead data_var = %d\n", pid, data_var);
}
data_var++;
sleep(10);
} while (true);
}
然后我执行:
slave_main &
master_main
967 Wrote data_var = 5678
967 Read data_var = 1234
970 Wrote data_var = 1234
970 Read data_var = 5678
^C
master_main
971 Wrote data_var = 1234
971 Error read
967 Wrote data_var = 1235
967 Read data_var = 1234
971 Wrote data_var = 1235
971 Read data_var = 1235
^C
[1]+ Broken pipe slave_main
直到 ^C,流程正在运行。
我重新启动主程序,它工作正常。发生错误读取是因为管道以非阻塞模式打开并且从属设备仍在休眠,因此尚未发送任何内容。在下一个循环中,我们看到两端的读/写工作正常。
当我杀死master时,slave在写操作唤醒后崩溃,可能是SIGPIPE信号。
如果我做反之亦然的版本(主人在背景中,杀死奴隶等)我得到类似的结果:重新打开工作。
我的问题仍然悬而未决。我的代码似乎可以在 Linux 上运行但是:它是确定性的吗?安全的? (不建议?这些问题涉及使用 mkfifo 重新打开管道(我承认有一些解决方法可以实现我想要实现的目标,但我的问题是出于知识的缘故而相当理论化,并且在某种程度上能够在需要时小心使用它) .
使用 mkfifo 创建一个 named pipe,一个进程将其打开为只读,另一个进程将其打开为只读,然后通过终止一个进程来关闭它的任何一个端点,并由一个新进程再次重新打开封闭端作为该代码在这篇文章中确实有效,并且似乎是安全的。
但是! 只有(重新)打开自己,而不是其他一切(即读取、写入等)。由于多种原因,如何使用和处理此类管道似乎很不安全(请参阅本问题评论部分中提到的注意事项,谢谢大家),因此明智地建议找到一种解决方法,例如在每个进程中打开两端以避免读/写在一端封闭的管道中进行操作,或者简单地找到一种方法来安全地关闭两侧的管道,然后像往常一样重新打开管道。