【问题标题】:FIFO pipe is always readable in select()FIFO 管道在 select() 中始终是可读的
【发布时间】:2013-01-30 00:11:14
【问题描述】:

在 C 伪代码中:

while (1) {
    fifo = open("fifo", O_RDONLY | O_NONBLOCK);
    fd_set read;
    FD_SET(fifo, &read);
    select(nfds, &read, NULL, NULL, NULL);
}

进程睡眠由select() 触发,直到另一个进程写入fifo。之后它总是会找到fifo 作为可读的文件描述符。

如何避免这种行为(即fifo被读取一次后,如何使其在再次写入之前被发现为不可读?)

【问题讨论】:

  • 那么,你想要发生什么?

标签: c select file-descriptor fifo


【解决方案1】:

您以只读方式打开该 FIFO (O_RDONLY),只要 FIFO 没有写入器,读取端就会收到EOF

选择系统调用将在EOF 上返回,并且对于您处理的每个EOF,都会有一个新的EOF。这就是观察到的行为的原因。

为了避免这种情况,为读写打开该 FIFO (O_RDWR)。这可确保您在 FIFO 上至少有一个写入器,因此不会有 EOF,因此除非有人写入该 FIFO,否则 select 不会返回。

【讨论】:

  • 这是防止运行 EOL 检测无限循环的正确答案
  • 这对我来说是正确的答案。虽然,在我的情况下,我有它的小变种:管道总是返回 2 个 fds - 1 用于读取,1 用于写入;就我而言,我明确关闭了写入端(遵循 C/S 应用程序中的代码示例)。因此,我遇到了同样的问题,即由于 EOF,读取端始终准备好读取。感谢您的提示
  • 天啊,4 个小时一直在寻找类似的东西。终于在这里找到了解决方案。 +1
  • 当我这样做时,我从未在选择块中从 fifo 的写入端注册响应。它只是停滞不前。即使我将字符串从终端重定向到先进先出,我仍然一无所获
【解决方案2】:

简单的答案是阅读直到read() 返回EWOULDBLOCK(或EAGAIN),或者出现错误。

除非您使用的操作系统(或运行时)有问题,否则您所说的根本不可能发生。否则你一定做错了什么。例如,select() 正在使用电平触发 I/O。我认为,您很可能没有完全耗尽套接字,因此select() 总是表明您在其中留下了一些东西(边缘触发的事件通知不会发生这种情况)。

下面是一个简单的示例,它显示了在read() 返回EWOULDBLOCK 之前应该如何读取以避免使描述符处于可读状态(我已经在 OS X 上编译并测试了它,而且几乎没有错误检查,但你应该明白):

/*
 * FIFO example using select.
 *
 * $ mkfifo /tmp/fifo
 * $ clang -Wall -o test ./test.c
 * $ ./test &
 * $ echo 'hello' > /tmp/fifo
 * $ echo 'hello world' > /tmp/fifo
 * $ killall test
 */

#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fd;
    int n;
    fd_set set;
    ssize_t bytes;
    size_t total_bytes;
    char buf[1024];

    fd = open("/tmp/fifo", O_RDWR | O_NONBLOCK);
    if (fd == -1) {
        perror("open");
        return EXIT_FAILURE;
    }

    FD_ZERO(&set);
    FD_SET(fd, &set);

    for (;;) {
        n = select(fd+1, &set, NULL, NULL, NULL);
        if (!n)
            continue;
        if (n == -1) {
            perror("select");
            return EXIT_FAILURE;
        }
        if (FD_ISSET(fd, &set)) {
            printf("Descriptor %d is ready.\n", fd);
            total_bytes = 0;
            for (;;) {
                bytes = read(fd, buf, sizeof(buf));
                if (bytes > 0) {
                    total_bytes += (size_t)bytes;
                } else {
                    if (errno == EWOULDBLOCK) {
                        /* Done reading */
                        printf("done reading (%lu bytes)\n", total_bytes);
                        break;
                    } else {
                        perror("read");
                        return EXIT_FAILURE;
                    }
                }
            }
        }
    }

    return EXIT_SUCCESS;
}

基本上,级别触发的 I/O 意味着如果有要阅读的内容,您会一直收到通知,即使您之前可能已经收到通知。相反,边缘触发的 I/O 意味着每次新数据到达时您只会收到一次通知,无论您是否阅读它都无关紧要。 select() 是电平触发的 I/O 接口。

希望对您有所帮助。祝你好运!

【讨论】:

  • 我认为另一个答案与这个答案相矛盾,并且是正确的。 select 在阻塞 read 将返回时返回,read 返回 0(发送 EOF)如果没有人打开您的 fifo 进行写入。您的示例程序从不这样做,因为它打开了 fifo 以供自己编写。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-12
  • 1970-01-01
  • 2017-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多