很奇怪,我似乎无法完成这项工作。
嗯,不是真的。你有奇怪的要求和脆弱的断言,你试图遵循。
读取器进程必须是阻塞的。
不……你为什么要做出这样的限制?考虑
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
在阅读器中,然后使用例如fchmod(fifofd, 0222)
更改其模式(0222 =-w--w--w-
)以允许作家。这些都不会阻碍。在读取器准备好之前,写入器打开 FIFO 的任何尝试都不会成功。
读写器不打开和关闭FIFO;它只是不停地打电话blocking_read()
.
如果写入器打开 FIFO 非阻塞只写(O_WRONLY | O_NONBLOCKING
),他们会失败errno = ENXIO
如果没有读者,或者有errno = EACCES
如果阅读器正在运行但尚未准备好。当有读者时,写入就会成功,除非读者跟不上。 (当读取器的缓冲区已满时,写入器将收到错误errno = EAGAIN
or errno = EWOULDBLOCK
.)
写入者可以轻松地进行非阻塞写入,并使用可自定义的超时来等待写入成为可能;这是一个非常相似的功能blocking_read()
above.
我希望 bytes_read 恰好等于 sizeof(Message_Struct_t) (24 字节),因为我将 writer 设置为原子。 24 字节小于 PIPE_BUF,因此只要不超过管道的大小限制,Linux 就保证它是原子的。
也许在最佳条件下。
例如,如果恶意用户执行以下操作:echo 1 > your_pipe
当作者在写消息时,你就失去了消息的边界。读取器获得两个字节(1
和换行符)和消息的初始部分,下一次读取将获取该消息的最后两个字节和下一条消息的初始部分,只要写入者写入套接字的速度与读取器读取的速度一样快或更快。
由于管道和 FIFO 永远不会保留消息边界,因此您的方法极其脆弱。更好的方法是使用保留消息边界的数据报套接字。
我绝对不会超出尺寸限制。我的作家程序就像客户;他们到来、执行并终止。因此管道的写入侧并不总是打开的。
您很容易超出大小限制。
只有一个管道缓冲区,如果写入者数量多于读取者可以跟上的数量,则它可能会变满(因此非阻塞写入会失败)。很容易导致这种情况发生:例如,如果读者这样做anything处理数据,使用两个并发编写器(如 Bashfor ((;;)) ; do printf 'message' > fifo ; done
) will填充缓冲区,并导致任何非阻塞编写器失败errno = EAGAIN
or errno = EWOULDBLOCK
.
这不仅仅是理论上的;而且是现实的。在实践中使用 Bash 和 mknod 很容易证明。
我有一种感觉,OP 正在构建一场灾难,等待他们当前的需求组合发生,特别是使用管道(或 FIFO)进行数据报传输。
就我个人而言,我会使用Unix域数据报套接字 http://man7.org/linux/man-pages/man7/unix.7.html绑定到路径名,可能/var/run/yourservice
。这将保证消息边界(两个不同的消息不会混合,就像管道或 FIFO 那样)。读者和作者都可以使用辅助数据 http://man7.org/linux/man-pages/man3/cmsg.3.html通过SCM_CREDENTIALS http://man7.org/linux/man-pages/man7/unix.7.html,它允许读取器验证写入器使用的用户 ID 和组 ID。
(编写者可以选择真实身份或有效身份。内核始终验证SCM_CREDENTIALS辅助消息字段,并且不会允许发送错误的数据。换句话说,SCM_CREDENTIALS辅助消息字段将始终是正确的在消息发送的那一刻.)
(请注意,使用数据报协议,读取器无法验证发送消息的进程的详细信息,因为当读取器收到 SCM_CREDENTIALS 辅助消息时,原始发送者可能已执行另一个进程,或者退出,操作系统重用该消息其他新进程的进程 ID。要验证使用哪个可执行文件发送消息,需要一种面向连接的协议,例如 Unix 域流套接字,写入器发送两到三条消息,所有消息都具有相同的 SCM_CREDENTIALS 辅助消息. 正确地做到这一点相当棘手,因此大多数程序员认为这种验证很糟糕。)