我用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;
}

到目前为止,只要两端都正常运行,就可以了.请注意,在我的代码中,实际上只有一个进程将使用mkfio create管道,而另一个进程将使用EEXIST失败.

当两个进程中的一个重新启动时,我的问题就出现了.如果另一个进程在停机时不使用管道,重新启动的进程会在调用我的函数open_pipe_read_modeopen_pipe_write_mode时崩溃/失败吗?或者,因为这些管道是用mkfio作为文件创建的,所以有可能像这样重新打开一个管道吗?请注意,在我的代码中,我考虑(忽略)来自mkfio的EEXIST错误.

我一直在读其他类似this one的问题,但我发现的都是"你不能"或"你为什么要这样做?就是不要这样做".我相信创建不带文件名的管道确实会让我不可能(即既麻烦又不安全,绝对不推荐)按照我的意图重新打开它们.

对此有什么建议(也就是说,如果它只在浏览未定义的行为效果时起作用,但它不能在任何随机点起作用)?

My actual question is:考虑到已经创建了一个文件名为mkfio的管道,并且其两端已被两个不同的进程打开,那么在管道的一端(无论是读还是写,阻塞或非阻塞)之前关闭并且另一端保持打开的情况下,是否有可能打开该管道的一端?

在重新启动这两个进程之前,我的代码都能正常工作,但我无法测试重启场景,因为我的项目很复杂.在开发几个简单的程序之前(主要是因为运行它们一次根本不代表决定论,更不用说好的做法了),我决定在互联网上查找,我惊讶地发现没有找到关于这个问题的任何问题/答案.

我测试过的东西

两个非常基本的程序:主机和从机,他们打开两个管道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之前,流程一直在运行.

我重新启动主程序,它运行得很好.之所以会出现读取错误,是因为管道以非阻塞模式打开,而从站仍处于Hibernate 状态,因此尚未发送任何内容.在下一个循环中,我们看到读/写对两端都很好.

当我杀死主设备时,从设备在写操作时唤醒后崩溃,可能是SIGPIPE信号.

如果我做相反的版本(BG中的主人,杀死奴隶,等等),我得到类似的结果:重新打开工作.

我的问题仍未解决.我的代码似乎可以在Linux上运行,但是:它是确定性的吗?安全?(不)推荐?这些问题指的是使用mkfio重新打开管道(我承认对于我想要实现的目标有一些变通的方法,但我的问题是为了获得知识,在某种程度上,能够在需要的时候谨慎地使用它).

推荐答案

使用mkfio创建一个named pipe,一个进程以只读方式打开它,另一个进程以只写方式打开它,然后通过终止一个进程来关闭它的任何末端,并像本文中的代码一样由一个新进程重新打开封闭的端,这是有效的,似乎是安全的.

However!只打开(重新)本身,而不打开其他所有东西(即读、写等).由于许多原因,如何使用和处理这样的管道似乎非常不安全(请参阅此问题的 comments 部分中提到的警告,谢谢大家),因此明智的建议是找到一种解决方法,例如在每个进程中打开两端,以避免在一端关闭的管道中进行读/写操作,或者简单地找到一种方法来安全地关闭两端的管道,然后照常重新打开管道.

Linux相关问答推荐

Bash脚本用于在远程工作后关闭用户会话

为什么Read()和cin.get()对输出缓冲区的影响不同?

为什么硬编码的阿拉伯字母与Unicode代码点不具有相同的值

Bash:将多行转换为单行的命令

如何更改文件的上次访问/修改/更改日期?

如何在vim中使用正则表达式来切换文件中所有字符的大小写

如何指定链接时使用的库版本?

如何找出哪个进程正在消耗等待 CPU(即 I/O 阻塞)

如何使用多个版本的 GCC

如何更改目录中所有文件中所有出现的单词

哪个程序在给定任何文件的情况下创建一个 C 数组?

无法在 Android Studio 中清理项目

Linux 上 pid_t、uid_t、gid_t 的大小

yum 可以告诉我哪些存储库提供了特定的包吗?

nvm:无法卸载当前活动的 node 版本

如何在python中找到文件或目录的所有者

如何显示正在运行的进程列表 Python?

将 jiffies 转换为毫秒

Windows 开发环境值得付出代价吗?

编译 OpenGL 程序 GL/glew.h 缺失