FIFO管道在select()中始终可读

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视为可读文件描述符。

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


那么,你想发生什么? - Dhaivat Pandya
3个回答

41

您以只读模式(O_RDONLY)打开了该FIFO,当没有写入者时,读端将接收到一个EOF信号。

Select系统调用会在EOF时返回,并且每次处理EOF都会出现新的EOF,这就是观察到的行为原因。

为了避免这种情况,请以读写模式(O_RDWR)打开该FIFO。这样可以确保至少有一个写入者在FIFO中,因此不会出现EOF,并且因此只有在有人向该FIFO写入数据时,select才会返回。


3
那是正确的答案,可以防止运行无尽的EOL检测循环。 - Big Papoo
这对我来说是正确的答案。但在我的情况下,我有一个微小的变体:管道总是返回2个文件描述符 - 1用于读取和1用于写入;在我的情况下,我明确关闭了写入端(遵循C/S应用程序的代码示例)。因此,我遇到了同样的问题,即由于EOF,读取端始终准备好读取。感谢提示。 - avner
哇,我找了将近4个小时才找到这样的解决方案。终于在这里找到了。+1 - siiikooo0743
当我这样做时,从FIFO的写端注册响应时,我从未收到任何响应。它只是停滞不前。即使我将字符串从终端重定向到FIFO,我仍然什么都没有得到。 - Jack Frye

3
简单的答案是一直读取,直到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接口。
希望这能帮到您。祝好运!

5
我认为另一个答案与这个相矛盾,且是正确的。select 在阻塞式 read 返回时返回,如果没有任何人打开你的 FIFO 进行写操作,则 read 返回 0(表示已到达文件结尾)。你的示例程序永远不会发生这种情况,因为它自己已经打开了 FIFO 进行写操作。 - Ben Millwood

0
我遇到了这个问题,可能是由于操作系统的一个bug。我发现在每次从select调用返回后关闭并重新打开fifo可以解决这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接