cfifoposix-select

C using select() to read from two named pipes (FIFO)


I am currently trying to write a program in C which will read from two named pipes and print any data to stdout as it becomes available.

for example: If I open two terminals and ./execute pipe1 pipe2 in one of the terminals (with pipe1 and pipe2 being valid named pipes) and then type echo "Data here." > pipe1 then the name of the pipe (here it is pipe1), the size, and the data should print to stdout-- Here it would look like pipe1 [25]: Data here.

I know I need to open the pipes with the O_RDONLY and O_NONBLOCK flags. I have looked at many examples (quite a few on this forum) of people using select() and I still don't understand what the different parameters being passed to select() are doing. If anyone can provide guidance here it would be hugely helpful. Below is the code I have so far.

 int pipeRouter(char[] fifo1, char[] fifo2){
    fileDescriptor1 = open(fifo1, O_RDONLY, O_NONBLOCK);
    fileDescriptor2 = open(fifo2, O_RDONLY, O_NONBLOCK);

    if(fileDescriptor1 < 0){
        printf("%s does not exist", fifo1);
    }
    if(fileDescriptor2 < 0){
        printf("%s does not exist", fifo2);
    }
}

Solution

  • The select lets you wait for an i/o event instead of waisting CPU cycles on read.

    So, in your example, the main loop can look like:

    for (;;)
    {
      int res;
      char buf[256];
    
      res = read(fileDescriptor1, buf, sizeof(buf));
      if (res > 0)
      {
          printf("Read %d bytes from channel1\n", res);
      }
      res = read(fileDescriptor2, buf, sizeof(buf));
      if (res > 0)
      {
          printf("Read %d bytes from channel2\n", res);
      }
    }
    

    If you add the code and run it, you would notice that:

    To solve issue, select and poll APIs are introduced. For select we need to know descriptors (we do), and the maximum out of them.

    So let's modify the code a bit:

     for (;;)
     {
        fd_set fds;
        int maxfd;
    
        FD_ZERO(&fds); // Clear FD set for select
        FD_SET(fileDescriptor1, &fds);
        FD_SET(fileDescriptor2, &fds);
    
        maxfd = fileDescriptor1 > fileDescriptor2 ? fileDescriptor1 : fileDescriptor2;
    
        select(maxfd + 1, &fds, NULL, NULL, NULL);
        // The minimum information for select: we are asking only about
        // read operations, ignoring write and error ones; and not
        // defining any time restrictions on wait.
    
    
       // do reads as in previous example here
     }
    

    When running the improved code, the CPU would not be wasted as much, but you will notice, that the read operation is performed even when there is no data for a particular pipe, but there is for another.

    To check, which pipe actually has the data, use FD_ISSET after select call:

    if (FD_ISSET(fileDescriptor1, &fds))
    {
       // We can read from fileDescriptor1
    }
    if (FD_ISSET(fileDescriptor2, &fds))
    {
       // We can read from fileDescriptor2
    }
    

    So, after joining said above, the code would look like:

    for (;;)
    {
      fd_set fds;
      int maxfd;
      int res;
      char buf[256];
    
      FD_ZERO(&fds); // Clear FD set for select
      FD_SET(fileDescriptor1, &fds);
      FD_SET(fileDescriptor2, &fds);
    
      maxfd = fileDescriptor1 > fileDescriptor2 ? fileDescriptor1 : fileDescriptor2;
    
      select(maxfd + 1, &fds, NULL, NULL, NULL);
    
    
      if (FD_ISSET(fileDescriptor1, &fds))
      {
         // We can read from fileDescriptor1
         res = read(fileDescriptor1, buf, sizeof(buf));
         if (res > 0)
         {
            printf("Read %d bytes from channel1\n", res);
         }
      }
      if (FD_ISSET(fileDescriptor2, &fds))
      {
         // We can read from fileDescriptor2
        res = read(fileDescriptor2, buf, sizeof(buf));
        if (res > 0)
        {
            printf("Read %d bytes from channel2\n", res);
        }
      }
    }
    

    So, add error handling, and you would be set.