Канал FIFO всегда доступен для чтения в select()

В псевдокоде 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 был прочитан один раз, как сделать так, чтобы он считался нечитаемым, пока он не получит другую запись?)


person Matoe    schedule 30.01.2013    source источник
comment
Итак, что вы хотите, чтобы произошло?   -  person Dhaivat Pandya    schedule 30.01.2013


Ответы (2)


Вы открыли этот FIFO только для чтения (O_RDONLY), когда в FIFO нет записи, конец чтения получит EOF.

Системный вызов Select вернется EOF, и для каждого EOF, который вы обрабатываете, будет новый EOF. В этом причина наблюдаемого поведения.

Чтобы избежать этого, откройте этот FIFO для чтения и записи (O_RDWR). Это гарантирует, что у вас есть хотя бы один писатель в FIFO, поэтому не будет EOF, и в результате select не вернется, если кто-то не запишет в этот FIFO.

person Fox    schedule 11.08.2013
comment
Это был правильный ответ, чтобы предотвратить запуск бесконечного цикла обнаружения EOL. - person Big Papoo; 23.01.2014
comment
это был правильный ответ для меня. Хотя в моем случае у меня был второстепенный вариант: pipe всегда возвращает 2 fds - 1 для чтения и 1 для записи; в моем случае я явно закрыл конец записи (следуя примеру кода из приложения C/S). Следовательно, я столкнулся с той же проблемой, что конец чтения всегда был готов к чтению из-за EOF. СПАСИБО ЗА СОВЕТ - person avner; 09.09.2014
comment
ОМГ, искал что-то подобное буквально 4 часа. Наконец нашел решение здесь. +1 - person siiikooo0743; 02.03.2016
comment
когда я делаю это, я никогда не регистрирую ответ в блоке выбора с конца записи fifo. просто останавливается. Даже если я перенаправлю строку с терминала в fifo, я все равно ничего не получу - person Jack Frye; 05.08.2018

Простой ответ — читать до тех пор, пока read() не вернет EWOULDBLOCK (или EAGAIN) или не вылетит с ошибкой.

То, что вы говорите, просто не может происходить, если используемая вами операционная система (или среда выполнения) не содержит ошибок. В противном случае вы должны делать что-то не так. Например, select() использует ввод-вывод по уровню. Я бы подумал, что, скорее всего, вы не полностью опорожняете сокет, и поэтому 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;
}

По сути, ввод-вывод, запускаемый уровнем, означает, что вы постоянно получаете уведомление, если есть что прочитать, даже если вы могли быть уведомлены об этом раньше. Напротив, ввод-вывод, запускаемый фронтом, означает, что вы получаете уведомление только один раз при каждом поступлении новых данных, и не имеет значения, прочитали вы их или нет. select() — это интерфейс ввода-вывода, управляемый уровнем.

Надеюсь, поможет. Удачи!

person Community    schedule 30.01.2013
comment
Я думаю, что другой ответ противоречит этому и является правильным. select возвращает, когда возвращается блокирующий read, а read возвращает 0 (сигнализируя EOF), если никто не открыл ваш fifo для записи. Ваш пример программы никогда не делает этого, потому что у него открыт fifo для записи. - person Ben Millwood; 22.05.2015