чтение из множества неблокирующих именованных каналов в linux

0

Основываясь на аналогичном примере, расположенном здесь в stackoverflow, у меня есть три именованных канала: pipe_a, pipe_b и pipe_c, которые передаются из внешних процессов. Я хотел бы иметь процесс чтения, который выводится на консоль, независимо от того, что написано на любом из этих каналов.

Нижеприведенная программа - это программа "все-в-одном", которая должна читать три канала неблокируемым образом и отображать вывод, когда какая-либо из труб получает новые данные.

Однако он не работает - он блокируется! Если pipe_a получает данные, он отобразит их, а затем дождитесь появления новых данных в pipe_b и т.д....

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

Может ли кто-нибудь помочь определить, почему трубы ведут себя так, как будто они находятся в режиме блокировки?

/*
 * 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>


// globals
int fd_a, fd_b, fd_c;
int nfd_a, nfd_b, nfd_c;
fd_set set_a, set_b, set_c;
char buffer_a[100*1024];
char buffer_b[100*1024];
char buffer_c[100*1024];


int readPipeA()
{
ssize_t bytes;
size_t total_bytes;

if (FD_ISSET(fd_a, &set_a)) {
    printf("\nDescriptor %d has new data to read.\n", fd_a);
    total_bytes = 0;
    for (;;) {
        printf("\nDropped into read loop\n");
        bytes = read(fd_a, buffer_a, sizeof(buffer_a));
        if (bytes > 0) {
            total_bytes += (size_t)bytes;
            printf("%s", buffer_a);
        } else {
            if (errno == EWOULDBLOCK) {
                printf("\ndone reading (%ul bytes)\n", total_bytes);
                break;
            } else {
                perror("read");
                return EXIT_FAILURE;
            }
        }
    }
}
}

int readPipeB()
{
ssize_t bytes;
size_t total_bytes;

if (FD_ISSET(fd_b, &set_b)) {
    printf("\nDescriptor %d has new data to read.\n", fd_b);
    total_bytes = 0;
    for (;;) {
        printf("\nDropped into read loop\n");
        bytes = read(fd_b, buffer_b, sizeof(buffer_b));
        if (bytes > 0) {
            total_bytes += (size_t)bytes;
            printf("%s", buffer_b);
        } else {
            if (errno == EWOULDBLOCK) {
                printf("\ndone reading (%ul bytes)\n", total_bytes);
                break;
            } else {
                perror("read");
                return EXIT_FAILURE;
            }
        }
    }
}
}

int readPipeC()
{
ssize_t bytes;
size_t total_bytes;

if (FD_ISSET(fd_c, &set_c)) {
    printf("\nDescriptor %d has new data to read.\n", fd_c);
    total_bytes = 0;
    for (;;) {
        printf("\nDropped into read loop\n");
        bytes = read(fd_c, buffer_c, sizeof(buffer_c));
        if (bytes > 0) {
            total_bytes += (size_t)bytes;
            printf("%s", buffer_c);
        } else {
            if (errno == EWOULDBLOCK) {
                printf("\ndone reading (%ul bytes)\n", total_bytes);
                break;
            } else {
                perror("read");
                return EXIT_FAILURE;
            }
        }
    }
}
}


int main(int argc, char* argv[])
    {


    // create pipes to monitor (if they don't already exist)
    system("mkfifo /tmp/PIPE_A");
    system("mkfifo /tmp/PIPE_B");
    system("mkfifo /tmp/PIPE_C");


    // open file descriptors of named pipes to watch
    fd_a = open("/tmp/PIPE_A", O_RDWR | O_NONBLOCK);
    if (fd_a == -1) {
    perror("open");
    return EXIT_FAILURE;
    }
    FD_ZERO(&set_a);
    FD_SET(fd_a, &set_a);


    fd_b = open("/tmp/PIPE_B", O_RDWR | O_NONBLOCK);
    if (fd_b == -1) {
    perror("open");
    return EXIT_FAILURE;
    }
    FD_ZERO(&set_b);
    FD_SET(fd_b, &set_b);


    fd_c = open("/tmp/PIPE_C", O_RDWR | O_NONBLOCK);
    if (fd_c == -1) {
    perror("open");
    return EXIT_FAILURE;
    }
    FD_ZERO(&set_c);
    FD_SET(fd_c, &set_c);



    for(;;)
    {
        // check pipe A
        nfd_a= select(fd_a+1, &set_a, NULL, NULL, NULL);
        if (nfd_a) {
            if (nfd_a == -1) {
                perror("select");
                return EXIT_FAILURE;
            }
            readPipeA();
        }

        // check pipe B
        nfd_b= select(fd_b+1, &set_b, NULL, NULL, NULL);
        if (nfd_b) {
            if (nfd_b == -1) {
                perror("select");
                return EXIT_FAILURE;
            }
            readPipeB();
        }

        // check pipe C
        nfd_c= select(fd_c+1, &set_c, NULL, NULL, NULL);
        if (nfd_c) {
            if (nfd_c == -1) {
                perror("select");
                return EXIT_FAILURE;
            }
            readPipeC();
        }
    }

    return EXIT_SUCCESS;
}

--- Обновлен код ---

Модифицированное приложение основано на обратной связи здесь, и еще немного чтения:

    /*
     * 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 <sys/time.h>
    #include <sys/types.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <unistd.h>


    int readPipe(int fd)
    {
        ssize_t bytes;
        size_t total_bytes = 0;
        char buffer[100*1024];

        printf("\nDropped into read pipe\n");
        for(;;) {
            bytes = read(fd, buffer, sizeof(buffer));
            if (bytes > 0) {
                total_bytes += (size_t)bytes;
                printf("%s", buffer);
            } else {
                if (errno == EWOULDBLOCK) {
                    printf("\ndone reading (%d bytes)\n", (int)total_bytes);
                    break;
                } else {
                    perror("read");
                    return EXIT_FAILURE;
                }
            }
        }
        return EXIT_SUCCESS;
    }


    int main(int argc, char* argv[])
    {
        int fd_a, fd_b, fd_c;   // file descriptors for each pipe
        int nfd;                // select() return value
        fd_set read_fds;        // file descriptor read flags
        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 0;

        // create pipes to monitor (if they don't already exist)
        system("mkfifo /tmp/PIPE_A");
        system("mkfifo /tmp/PIPE_B");
        system("mkfifo /tmp/PIPE_C");

        // open file descriptors of named pipes to watch
        fd_a = open("/tmp/PIPE_A", O_RDWR | O_NONBLOCK);
        if (fd_a == -1) {
            perror("open");
            return EXIT_FAILURE;
        }

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

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

        FD_ZERO(&read_fds);
        FD_SET(fd_a, &read_fds);  // add pipe to the read descriptor watch list
        FD_SET(fd_b, &read_fds);
        FD_SET(fd_c, &read_fds);

        for(;;)
        {
            // check if there is new data in any of the pipes
            nfd = select(fd_a+1, &read_fds, NULL, NULL, &tv);
            if (nfd != 0) {
                if (nfd == -1) {
                    perror("select");
                    return EXIT_FAILURE;
                }

                if (FD_ISSET(fd_a, &read_fds)) {
                    readPipe(fd_a);
                }
            }

            nfd = select(fd_b+1, &read_fds, NULL, NULL, &tv);
            if (nfd != 0) {
                if (nfd == -1) {
                    perror("select");
                    return EXIT_FAILURE;
                }

                if (FD_ISSET(fd_b, &read_fds)){
                    readPipe(fd_b);
                }
            }
            nfd = select(fd_c+1, &read_fds, NULL, NULL, &tv);
            if (nfd != 0) {
                if (nfd == -1) {
                    perror("select");
                    return EXIT_FAILURE;
                }
                if (FD_ISSET(fd_c, &read_fds)){
                    readPipe(fd_c);
                }
            }

            usleep(10);
        }
        return EXIT_SUCCESS;
    }

Все еще проблема с выбором return zero (0), когда есть данные, ожидающие в любой из наблюдаемых труб? Я не должен правильно использовать функции select() и fd_isset(). Вы видите, что я делаю неправильно? Благодарю.

  • 0
    Сузьте свою проблему. Сделайте тестовый пример. Это слишком много кода. Например, проблема повторяется только с двумя трубами? Тогда ваш код здесь будет только 66% от этого размера.
  • 0
    Проблема в том, что функция выбора блокирует. Я понял select (), чтобы проверить флаги, чтобы увидеть, будет ли чтение «блокировать», если оно было выполнено, так что можно решить, выполнять чтение или нет. Труба открывается в режимах RDWR и NONBLOCK. Параметры для выбора, возможно, должны быть изменены, что я проверю дальше.
Показать ещё 2 комментария
Теги:
named-pipes

4 ответа

2
Лучший ответ

Проблема в том, что функция select блокируется. Я понял select(), чтобы проверять флаги, чтобы увидеть, будет ли чтение "блокироваться", если оно было выполнено, чтобы можно было выполнить чтение или нет. Труба открывается в режиме RDWR и NONBLOCK.

Вы говорите, что проблема заключается в том, что функция select блокируется, но продолжайте признавать, что флаг NONBLOCK делает это так, чтобы чтение блокировалось. Выбор и чтение - это две разные вещи.

Флаг O_NONBLOCK влияет на сокет (и, следовательно, ваши read вызовы); он не изменяет поведение select, которое имеет свою собственную тайм-аут/блокировку семантики.

man select утверждает, что аргумент timeout с обоими числовыми членами, установленными на ноль, производит неблокирующий опрос, тогда как аргумент таймаута NULL может привести к неопределенному блоку:

Если параметр тайм-аута является нулевым указателем, то вызов pselect() или select() должен блокироваться неограниченно, пока, по крайней мере, один дескриптор не выполнит указанные критерии. Чтобы осуществить опрос, параметр времени ожидания не должен быть пустым указателем, и должен указывать на нулевую стоимость TimeSpec структуры формата: первый формата.

(NB текст далее вверх страницы показывает, что, хотя pselect() принимает структуру timespec, select() принимает структуру timeval, я timeval применить эту логику к приведенной выше цитате.)

Таким образом, перед каждым вызовом select timeval, установите его члены в ноль и передайте это для select.

Несколько нот, пока мы здесь:

  1. В идеале у вас будет только один вызов select, одновременно проверяющий все три дескриптора файла, а затем решая, какие каналы read, проверяя ваш набор FD с помощью fd_isset;

  2. Я также предлагаю немного usleep в конце вашего тела цикла, иначе ваша программа начнет вращаться действительно, очень быстро, когда голодает информация.

  • 0
    Я даже не уверен, что это главная проблема ОП. У него есть 3 отдельных select с 3 отдельными наборами. Они должны быть объединены в один.
  • 0
    @Duck: Они должны, но я не понимаю, как это может вызвать его проблемы.
Показать ещё 4 комментария
0

Я взял фрагмент, который я использовал для программирования сокетов, но он должен работать одинаково для именованных каналов. Это должно быть просто и легко следовать.

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>

int main()
{
  fd_set readSet, writeSet, exSet;
  struct timeval tv;
  int i;

  int fifoFds[3];

  //open files or named pipes and put them into fifoFds array

  while(1)
  {

    FD_ZERO(&readSet);
    FD_ZERO(&writeSet); //not used
    FD_ZERO(&exSet); //not used

    int maxfd = -1;
    for(i = 0; i < 3; i++)
    {
      if(maxfd == -1 || fifoFds[i] > maxfd) 
        maxfd = fifoFds[i];

      FD_SET(fifoFds[i], &readSet);
    }

    tv.tv_sec = 1; //wait 1 second in select, change these as needed
    tv.tv_usec = 0; //this is microseconds

    select(maxfd+1, &readSet, &writeSet, &exSet, &tv);

    for(i = 0; i < 3; i++)
    {
      if(FD_ISSET(fifoFds[i], &readSet))
      {
        //Read from that fifo now!
      }
    }

  }

  return 0;
}
0

Просто для упрощения вашего кода. Вам не нужно три выбора. Вы можете установить все дескрипторы файлов с тремя вызовами FD_SET(), выбрать вызов, а if nfd > 0 проверьте каждый fd_x with FD_ISSET().

0

Вот мое рабочее решение для чтения трех именованных каналов. Его можно было бы оптимизировать несколькими способами, но, как написано, для всех, кто должен это сделать, должно быть очень ясно:

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


    int readPipe(int fd)
    {
        ssize_t bytes;
        size_t total_bytes = 0;
        char buffer[100*1024];

        printf("\nReading pipe descriptor # %d\n",fd);
        for(;;) {
            bytes = read(fd, buffer, sizeof(buffer));
            if (bytes > 0) {
                total_bytes += (size_t)bytes;
                printf("%s", buffer);
            } else {
                if (errno == EWOULDBLOCK) {
                    break;
                } else {
                    perror("read error");
                    return EXIT_FAILURE;
                }
            }
        }
        return EXIT_SUCCESS;
    }


    int main(int argc, char* argv[])
    {
        int fd_a, fd_b, fd_c;   // file descriptors for each pipe
        int nfd;                // select() return value
        fd_set read_fds;        // file descriptor read flags
        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 0;

        // create pipes to monitor (if they don't already exist)
        system("mkfifo /tmp/PIPE_A");
        system("mkfifo /tmp/PIPE_B");
        system("mkfifo /tmp/PIPE_C");

        // open file descriptors of named pipes to watch
        fd_a = open("/tmp/PIPE_A", O_RDWR | O_NONBLOCK);
        if (fd_a == -1) {
            perror("open error");
            return EXIT_FAILURE;
        }

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

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

        for(;;)
        {
            // clear fds read flags
            FD_ZERO(&read_fds);

            // check if there is new data in any of the pipes
            // PIPE_A
            FD_SET(fd_a, &read_fds);
            nfd = select(fd_a+1, &read_fds, NULL, NULL, &tv);
            if (nfd != 0) {
                if (nfd == -1) {
                    perror("select error");
                    return EXIT_FAILURE;
                }
                if (FD_ISSET(fd_a, &read_fds)) {
                    readPipe(fd_a);
                }
            }

            // PIPE_B
            FD_SET(fd_b, &read_fds);
            nfd = select(fd_b+1, &read_fds, NULL, NULL, &tv);
            if (nfd != 0) {
                if (nfd == -1) {
                    perror("select error");
                    return EXIT_FAILURE;
                }
                if (FD_ISSET(fd_b, &read_fds)){
                    readPipe(fd_b);
                }
            }

            // PIPE_C
            FD_SET(fd_c, &read_fds);
            nfd = select(fd_c+1, &read_fds, NULL, NULL, &tv);
            if (nfd != 0) {
                if (nfd == -1) {
                    perror("select error");
                    return EXIT_FAILURE;
                }
                if (FD_ISSET(fd_c, &read_fds)){
                    readPipe(fd_c);
                }
            }

            usleep(100000);
        }
        return EXIT_SUCCESS;
    }
  • 0
    Это работает, как ожидается, работает на OS X
  • 0
    запуск этого же кода в Linux не работает ... Флаг O_RDWR возвращает неожиданные результаты для FIFO. Mac справляется, но O_RDONLY при запуске Linux

Ещё вопросы

Сообщество Overcoder
Наверх
Меню