我有两个不同的C++程序,它们通过(Linux)管道相互通信。
一个进程是主进程,另一个进程是从进程。它们中的任何一个都可能崩溃或简单地重新启动,但永远不会同时发生(让我们假设这一点)。
从设备以阻塞方式在写模式中创建并打开管道A(不相关的最后特征),然后也以阻塞方式在读模式中创建并打开管道B(再次,不相关的最后特征)。
主设备以非阻塞方式在读取模式中创建并打开管道A(再次,不相关的最后特征),然后它等待一段时间,使得从设备可以退出管道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 * 创建 * 管道,而另一个进程将使用EEXIST失败。
当两个进程中的一个重新启动时,我的问题就会出现。如果另一个进程在停机期间没有使用管道,重新启动的进程在调用我的函数open_pipe_read_mode
或open_pipe_write_mode
时会崩溃/失败吗?或者,因为这些管道是用mkfifo作为文件创建的,所以可以像这样重新打开一个管道?请注意,在我的代码中,我考虑(忽略)来自mkfifo的EEXIST错误。
我一直在阅读其他问题,如this one,但我发现的是“你不能”或“为什么你会?不要”。我相信创建没有文件名的管道确实是不可能的(即笨重而且不安全,绝对不推荐)按照我的意图重新打开它们。
是否有任何建议反对这一点(即,如果它只适用于冲浪未定义的行为效果,但它不能在任何随机点工作)?
我的实际问题是:考虑到已经创建了一个文件名为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,流程才开始工作。
我重新启动主程序,它工作正常。发生错误读取是因为管道在非阻塞模式下打开,而从机仍在睡眠,因此尚未发送任何内容。在下一个循环中,我们看到读/写在两端都工作正常。
当我杀死主机时,从机在写操作唤醒后崩溃,可能是SIGPIPE信号。
如果我做相反的版本(主在bg中,杀死从等),我得到类似的结果:重新开放工程。
我的问题仍然没有答案。我的代码似乎在Linux上工作,但是:它是确定性的吗?安全吗?不推荐。这些问题涉及到用mkfifo重新打开管道(我承认我想实现的目标有一些变通方法,但我的问题是为了知识而理论化的,在某种程度上,能够在需要时小心使用它)。
1条答案
按热度按时间pkmbmrz71#
用mkfifo创建一个命名管道,一个进程以只读方式打开它,另一个进程以只写方式打开它,然后通过终止一个进程来关闭它的任何一端,并通过一个新进程重新打开关闭的一端,就像这篇文章中的代码一样,工作正常,似乎是安全的。
**然而!**只有(重新)打开自己,而不是其他一切(即读取,写入等)。由于许多原因,如何使用和处理这样的管道似乎非常不安全(请参阅此问题的评论部分中提到的警告,谢谢大家),因此明智地建议找到一种解决方案,例如在每个进程中打开两端,以避免在一端关闭的管道中进行读/写操作,或者简单地找到一种方法来安全地关闭管道的两端,然后像往常一样重新打开管道。