我用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_mode
或open_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重新打开管道(我承认对于我想要实现的目标有一些变通的方法,但我的问题是为了获得知识,在某种程度上,能够在需要的时候谨慎地使用它).