在Linux中无法阻止从命名管道(FIFO)读取
我似乎无法完成这项工作,这很奇怪。 这是我的架构:我有一个命名管道,它将在一个始终运行的 root
读取器进程和多个应用程序编写器进程之间进行通信。 当编写器nonblocking
blocking
时,读取器进程必须被blocking
。 因此,这是我在读者进程中所做的,它将以root
权限运行。
reader.c
#define PIPE_ID "/dev/shm/mypipe" // This function configures named pipe void configure_pipe() { // So that others can write umask(0000); if(mkfifo(PIPE_ID, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH | S_IWGRP | S_IWOTH) != 0) { perror("mkfifo error\n"); } }
在主要function:
int main (int argc, char **argv) { int Process_Pipe_ID; configure_pipe(); // main loop which never ends while(1) { if((Process_Pipe_ID = open(PIPE_ID, O_RDONLY | O_NONBLOCK)) < 0) { perror("pipe file open error\n"); exit(-1); } int bytes_read = 0; Message_Struct_t msg; // loop to keep reading from the pipe if there are messages to be read while((bytes_read = read(Process_Pipe_ID, &msg, sizeof(Message_Struct_t))) != 0) { if(bytes_read < 0) { perror("Pipe reading error in Scheduling agent\n"); exit(-1); } printf("Read: %d, first: %d, second: %d\n", bytes_read, msg.first, msg.second); fflush(stdout); } close(Process_Pipe_ID); } }
我希望这个阅读器在open
不被阻止,但如果管道上有东西,它应该继续读取命名管道。 然后,如果它收到0
表示EOF
(管道中没有可用的),那么它应该关闭文件描述符并再次打开它以继续尝试从管道读取。 有点忙着等待 。
我希望bytes_read
完全是sizeof(Message_Struct_t)
(24字节),因为我将我的bytes_read
设置为primefaces。 24个字节小于PIPE_BUF
,因此Linux确保它是primefaces的,只要我不超过管道的大小限制。 我没办法超过尺寸限制。 我的作家programs
就像客户; 他们来,执行和终止。 所以管道的书写面并不总是open
。 这是我非常简单的作家:
writer.c
void writeInts(int first, int second) { Process_Pipe_ID = open(PIPE_ID, O_WRONLY | O_NONBLOCK); Message_Struct_t msg; msg.first = first; msg.second = second; int num; if((num = write(Process_Pipe_ID, &msg, sizeof(msg))) < 0) { perror("Error in writing\n"); exit(-1); } else printf("%d bytes wrote to pipe.\n", num); close(Process_Pipe_ID); }
但是,我的输出非常奇怪。 在我按下enter
键之前,屏幕上没有任何内容(对于reader.c
)。 当我按回车键时,我得到以下内容:
Read: 1, first: 0, second: 0 Read: 1, first: 0, second: 0 Read: 1, first: 0, second: 0
当我按下其他键然后enter
,我得到了这个:
Read: 1, first: 0, second: 0 aa Read: 3, first: 0, second: 0 aaa Read: 4, first: 0, second: 0
我不知道到底发生了什么以及如何解决这个问题。 我希望在写入器是非阻塞和primefaces的时候进行阻塞读取。 我已经搜索了很多,然后编写了代码,但是我无法让它工作是非常奇怪的。
我似乎无法完成这项工作,这很奇怪。
嗯,不是真的。 你有很奇怪的要求和脆弱的断言。
读者进程必须是阻止的。
Noooo ……你为什么要这样限制? 考虑
ssize_t blocking_read(fd, void *buf, size_t len) { struct timeval timeout; fd_set fds; int r; ssize_t n; /* First, do a speculative read, just in case there is data already available. */ n = read(fd, buf, len); if (n >= 0) return n; else if (n != -1) { /* Paranoid check, will never happen .*/ errno = EIO; return -1; } else if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; /* Wait for data to become available. */ FD_ZERO(&fds); while (1) { FD_SET(fd, &fds); timeout.tv_sec = 60; /* One minute */ timeout.tv_usec = 0; /* and no millionths of seconds */ r = select(fd + 1, &fds, NULL, NULL, NULL, &timeout); if (r < 0) return -1; /* errno set by select() */ else if (!r) continue; /* Timeout */ n = read(fd, buf, len); if (n >= 0) return n; else if (n != -1) { /* Paranoid check, will never happen .*/ errno = EIO; return -1; } else if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; } }
它就像阻塞和非阻塞描述符一样阻塞读取。 如果没有任何反应,它每分钟会唤醒一次,但你可以调整它,直到它足够长并不重要。 (我会考虑一秒到86400秒之间的值,大约一天。更长的只是愚蠢。记住它是超时,而不是普通的睡眠:任何信号传递或传入数据都会立即将其唤醒。)
这样,您可以在0400模式下初始创建FIFO( r--------
),打开它O_RDONLY | O_NONBLOCK
读取器中的O_RDONLY | O_NONBLOCK
,然后使用例如fchmod(fifofd, 0222)
来改变其模式(0222 = -w--w--w-
)以允许写入器。 这些都没有阻止。 在读者准备好之前,编写器尝试打开FIFO的所有操作都不会成功。
读卡器不会打开和关闭FIFO; 它只是一直调用blocking_read()
。
如果编写器打开FIFO非阻塞只写( O_WRONLY | O_NONBLOCKING
),如果没有读卡器,它们将失败, errno = ENXIO
如果读卡器正在运行但尚未就绪,则errno = EACCES
。 当有读者时,写入将成功,除非读者无法跟上。 (当读者的缓冲区已满时,编写者将收到errno = EAGAIN
或errno = EWOULDBLOCK
。)
编写者可以轻松地进行非阻塞写操作,并具有可自定义的超时,等待写入成为可能; 它与上面的blocking_read()
非常相似。
我希望bytes_read完全是sizeof(Message_Struct_t)(24字节),因为我将我的编写器设置为primefaces。 24个字节小于PIPE_BUF,因此Linux确保它是primefaces的,只要我不超过管道的大小限制。
或许,在最佳条件下。
例如,如果恶意用户例如在作者正在编写消息时echo 1 > your_pipe
,则会丢失消息边界。 读者获取两个字节( 1
和换行符)和消息的初始部分,下一个读取获取该消息的最后两个字节和下一个消息的初始部分,只要有写入者写入套接字快于或快于读者可读。
由于管道和FIFO从不保留消息边界,因此您的方法非常脆弱。 更好的方法是使用保留消息边界的数据报套接字。
我没办法超过尺寸限制。 我的作家节目就像客户; 他们来,执行和终止。 所以管道的书写面并不总是开放的。
您可以轻松超过大小限制。
只有一个管道缓冲区,它可以变满(因此非阻塞写入失败),如果有更多的编写器,读者可以跟上。 很容易导致这种情况发生:例如,如果读者对数据做了任何事情 ,使用两个并发写入器(如Bash for ((;;)) ; do printf 'message' > fifo ; done
) 将填充缓冲区,并导致任何非阻塞编写器失败, errno = EAGAIN
或errno = EWOULDBLOCK
。
这不仅仅是理论上的; 在实践中使用Bash和mknod很容易certificate。
我有一种感觉OP正在构建一个灾难,等待他们当前的需求组合,特别是使用管道(或FIFO)进行数据报传输。
就个人而言,我会使用绑定到路径名的Unix域数据报套接字 ,可能是/var/run/yourservice
。 这将保证消息边界(两个不同的消息不会混合,就像管道或FIFO一样)。 读者和编写者都可以使用辅助数据来传递SCM_CREDENTIALS ,这允许读者validation作者使用的用户ID和组ID。
(作者可以在真实或有效身份之间进行选择。内核总是validationSCM_CREDENTIALS辅助消息字段,并且不允许发送不正确的数据。换句话说,SCM_CREDENTIALS辅助消息字段在消息出现时始终是正确的。送了 。)
(请注意,使用数据报协议,读者无法validation发送消息的进程的详细信息,因为当读者收到SCM_CREDENTIALS辅助消息时,原始发件人可能已执行另一个进程,或退出操作系统重用一些其他新进程的进程ID。要validation用于发送消息的可执行文件,需要一个面向连接的协议,如Unix域流套接字,编写器发送两条或三条消息,所有消息都具有相同的SCM_CREDENTIALS辅助消息这样做是非常棘手的,所以大多数程序员都认为这样的validation很糟糕。)