从linux中的多个非阻塞命名管道读取

时间:2014-01-02 02:10:52

标签: c++ c linux named-pipes

基于stackoverflow中位于here的类似示例, 我有三个命名管道,pipe_a,pipe_b和pipe_c,它们是从外部进程提供的。我希望有一个读取器进程输出到控制台,无论写入任何这些管道。

下面的程序是一个一体化的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;
    }

当在任何一个观察的管道中有数据等待时,选择返回零(0)仍有问题吗?我一定不能正确使用select()和fd_isset()。你能看出我做错了什么吗?感谢。

4 个答案:

答案 0 :(得分:2)

  

问题是select函数是阻塞的。我理解select()来检查标志,看看如果执行了读取“将”阻止,以便可以决定是否执行读取。管道正在RDWR和NONBLOCK模式下打开。

你说的问题是 select 功能正在阻塞,但继续承认NONBLOCK标志只是为了让块。选择和阅读是两回事。

O_NONBLOCK标志会影响套接字(以及您的read次呼叫); not 改变了select的行为,它有自己的超时/阻塞语义。

man select声明两个数字成员都设置为零的timeout参数会产生非阻塞轮询,而超时参数NULL可能会导致无限期阻塞:

  

如果 timeout 参数是空指针,则对 pselect() select()的调用将无限期地阻塞,直至至少为止一个描述符符合指定的标准。要进行轮询, timeout 参数不应为空指针,并且应指向零值 timespec timeval < / strong>结构。

(NB。页面上方的文字表明,虽然pselect()采用timespec结构,select()采用timeval结构;我已采取将这种逻辑应用于上述引文的自由。)

因此,在每个select调用构建timeval之前,将其成员设置为零,并将其传递给select

有几个笔记,我们在这里:

  1. 理想情况下,您只需要进行一次 select调用,一次检查所有三个文件描述符,然后通过检查FD确定哪些管道到read设置为fd_isset;

  2. 我还建议在你的循环体末端添加一点usleep,否则你的程序会在缺乏数据的情况下真正快速地旋转。

答案 1 :(得分: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;
    }

答案 2 :(得分:0)

只是为了让您的代码更简单。你不需要三个选择。您可以设置所有免费文件描述符,其中包含三个调用FD_SET(),调用select,if nfd > 0检查每个fd_x with FD_ISSET()

答案 3 :(得分: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;
}