简体   繁体   English

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

[英]Reading from multiple nonblocking named pipes in Linux

Building on a similar example located here in stackoverflow, I have three named pipes, pipe_a, pipe_b, and pipe_c that are being fed from external processes.在位于一个类似的例子号楼这里的计算器,我有三个命名管道,pipe_a,pipe_b和pipe_c正在从外部进程喂养。 I'd like to have a reader process that outputs to the console, whatever is written to any of these pipes.我想要一个输出到控制台的读取器进程,无论写入这些管道中的任何一个。

The program below is an all-in-one c program that should read the three pipes in a non-blocking manner, and display output when any one of the pipes gets new data.下面的程序是一个一体化的c程序,它应该以非阻塞的方式读取三个管道,并在任何一个管道获取新数据时显示输出。

However, it isn't working - it is blocking!但是,它不起作用 - 它阻塞了! If pipe_a gets data, it will display it and then wait for new data to arrive in pipe_b, etc...如果 pipe_a 得到数据,它会显示它,然后等待新数据到达 pipe_b 等......

select() should allow the monitoring of multiple file descriptors until one is ready, at which time we should drop into the pipe's read function and get the data. select() 应该允许监视多个文件描述符,直到一个文件描述符准备就绪,此时我们应该进入管道的读取函数并获取数据。

Can anyone help identify why the pipes are behaving like they are in blocking mode?任何人都可以帮助确定为什么管道的行为就像处于阻塞模式一样?

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

--- Updated Code --- --- 更新代码 ---

Modified the application based on the feedback here, and some more reading:根据这里的反馈修改了应用程序,还有一些阅读:

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

Still having an issue with the select returning zero (0) when there is data waiting in any one of the watched pipes?当任何一个受监视的管道中有数据等待时,仍然有选择返回零 (0) 的问题? I must not be using the select() and fd_isset() correctly.我一定没有正确使用select()fd_isset() Can you see what I'm doing wrong?你能看出我做错了什么吗? Thanks.谢谢。

The issue is that the select function is blocking.问题是 select 函数被阻塞了。 I understood select() to check flags to see if the read "would" block if it was performed, so that one can decide to perform the read or not.我理解 select() 检查标志以查看读取是否“会”阻止执行,以便可以决定是否执行读取。 The pipe is being opened in RDWR and NONBLOCK mode.管道正在以 RDWR 和 NONBLOCK 模式打开。

You say the problem is that the select function is blocking, but go on to admit that the NONBLOCK flag only makes it so that the read would block.您说问题在于select函数正在阻塞,但继续承认NONBLOCK标志只会使读取阻塞。 Select and read are two different things.选择和阅读是两件不同的事情。

The O_NONBLOCK flag affects the socket (and, consequently, your read calls); O_NONBLOCK标志影响套接字(因此,你的read调用); it does not change the behaviour of select , which has its own timeout/blocking semantics.不会改变的行为select ,它有自己的超时/阻塞语义。

man select states that a timeout argument with both numeric members set to zero produces a non-blocking poll, whereas a timeout argument of NULL may lead to an indefinite block: man select声明两个数字成员都设置为零的timeout参数会产生非阻塞轮询,而NULL的超时参数可能会导致无限阻塞:

If the timeout parameter is a null pointer, then the call to pselect() or select() shall block indefinitely until at least one descriptor meets the specified criteria.如果超时参数是空指针,则对pselect()select()的调用将无限期阻塞,直到至少有一个描述符满足指定的标准。 To effect a poll, the timeout parameter should not be a null pointer, and should point to a zero-valued timespec timeval structure.要实现轮询,超时参数不应是空指针,而应指向零值的 timespec timeval结构。

(NB. text further up the page indicates that, though pselect() takes a timespec structure, select() takes a timeval structure; I've taken the liberty of applying this logic to the above quotation.) (注意,页面上方的文本表明,尽管pselect()采用timespec结构,但select()采用timeval结构;我已冒昧地将此逻辑应用于上述引用。)

So, before each select call construct a timeval , set its members to zero, and pass that to select .因此,在每个select调用构造一个timeval ,将其成员设置为零,并将其传递给select

A couple of notes, while we're here:一些注意事项,当我们在这里时:

  1. Ideally you'd only have one select call, checking all three file descriptors at once, then deciding which pipes to read from by checking your FD set with fd_isset ;理想情况下,您只有一个select调用,一次检查所有三个文件描述符,然后通过使用fd_isset检查您的 FD 集来决定要read管道;

  2. I also suggest putting a little usleep at the end of your loop body, otherwise your program is going to spin really, really quickly when starved of data.我还建议在循环体的末尾放一点usleep ,否则你的程序会在缺乏数据时非常非常快地旋转。

Here is my working solution for reading the three named pipes.这是我读取三个命名管道的工作解决方案。 It could be optimized in a few ways, but as its written, it should be very clear for anyone else who needs to do this:它可以通过几种方式进行优化,但正如它所写的那样,对于需要这样做的其他人来说应该非常清楚:

    #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;
    }

Just for making your code simpler.只是为了让你的代码更简单。 You don't need three selects.你不需要三个选择。 You can set all free file descriptors with three calls FD_SET() , call select, and if nfd > 0 check each fd_x with FD_ISSET() .您可以通过三个调用FD_SET()FD_SET()来设置所有空闲文件描述符, if nfd > 0if nfd > 0 fd_x with FD_ISSET()检查每个fd_x with FD_ISSET()

I took a snippet I used for socket programming, but it should work the same for named pipes.我使用了一个用于套接字编程的代码段,但它对于命名管道的工作方式应该相同。 It should be simple and easy to follow.它应该简单易懂。

#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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM