Musicode
Musicode

Reputation: 661

Communicating Data through Pipes in C between multiple child processes (UNIX Environment)

Today I am running into a small problem in C where I cannot seem to figure out why my processes are not "lining up" in how they use a pipe.

I have 2 child processes, forked from a parent process, which are writing some data, and they are supposed to write this data into the pipe. The pipe is then periodically read by the parent process, and "emptied out" of the data in the pipe, and printed to the terminal line. Then, the whole thing repeats itself.

//Creates Three Processes A,B,C which communicate data through a pipe.
int main(void) {
  int     fd[2], nbytes;
  pid_t   childpidB, childpidC;
  char    readbuffer[100];
  readbuffer[0] = '\0';
  pipe(fd);

  if((childpidB = fork()) < 0)
    {
      perror("fork B error.");
      exit(1);
    }
  if(childpidB == 0)
    {
      close(fd[0]);
      char AAA[3] = {'A','A','A'};
      int j = 0;
      int k = 1;
      while (j < 500) {
        if(j%100==0 && j!=0) {
          usleep(100);
        }
        char numbers[6];
        numbers[0] = '\0';
        sprintf(numbers,"%03dAAA",k);
        k++;
        j++;
        write(fd[1], numbers, (strlen(numbers)+1));
      }
      close(fd[1]);
      exit(0);
    }
  if((childpidC = fork()) < 0)
    {
      perror("fork C error.");
      exit(1);
    }
  if(childpidC == 0)
    {
      close(fd[0]);
      int i = 0;
      char c = 'A';
      int numberafter = 0;
      while (i < 260) {
        if(i%60==0 && i!=0) {
          usleep(300);
        }
      char dump[3];
      dump[0] = '\0';
        if(c=='[') { c='A'; numberafter++; }
        sprintf(dump,"%cx%d\n",c,numberafter);
        c++;
        write(fd[1], dump, (strlen(dump)+1));    
        i++;
      }
      close(fd[1]);
      exit(0);
    }

  //Process A
  if(childpidC > 0 && childpidB > 0) {
    int x = 0;
    close(fd[1]);
    while (nbytes = read(fd[0], readbuffer, sizeof(readbuffer)) > 0) {
      if(x%50==0 && x!=0) {
    usleep(200);
      }
      printf("%s\n", readbuffer);
      x++;
    }
    close(fd[0]);
    exit(0);
  }
}

My issue is that the parent process is not always reading 100 characters from the pipe, and I'm not sure how this can even happen. It is only reading a small fragment of the data from each pipe, before breaking. It is supposed to read 100 characters from the pipe each time. Here is an example of the output:

001AAA
Ax0

Gx0

Ox0
...(keeps going for a while)...

I am wondering why/how it is only reading a small amount of data from the pipe each time. The data it is reading actually looks correct, but it's just not reading 100 characters per line (as I thought it logically should be, in my code). It also then somehow reads nothing from the pipe, and writes just a blank line (because it read no characters, but still prints a blank line). This must mean that the child processes are only writing a few characters into the pipe at each iteration, somehow, but I don't understand how that can be either. The output is also different each time the program is run.

It seems as if the child processes and the parents are not synchronized, but I thought that closing the appropriate slot of fd[0] or fd[1] would account for this.

EDIT: I have updated the code to get closer to what I want (or perhaps exactly what I want, need more testing). It had to do with passing too many NULL characters.

int main(void) {
  int     fd[2], nbytes;
  pid_t   childpidB, childpidC;
  char    readbuffer[100];
  readbuffer[0] = '\0';
  pipe(fd);

  if((childpidB = fork()) < 0)
    {
      perror("fork A error.");
      exit(1);
    }
  if(childpidB == 0)
    {
      close(fd[0]);
      char AAA[3] = {'A','A','A'};
      int j = 0;
      int k = 1;
      while (j < 500) {
    if(j%100==0 && j!=0) {
      usleep(100);
    }
    char numbers[6];
    numbers[0] = '\0';
    sprintf(numbers,"%03dAAA",k);
    k++;
    j++;
    write(fd[1], numbers, (strlen(numbers)));
      }
      close(fd[1]);
      exit(0);
    }
  if((childpidC = fork()) < 0)
    {
      perror("fork B error.");
      exit(1);
    }
  if(childpidC == 0)
    {
      close(fd[0]);
      int i = 0;
      char c = 'A';
      int numberafter = 0;
      while (i < 260) {
    if(i%60==0 && i!=0) {
      usleep(300);
    }
    char dump[3];
    dump[0] = '\0';
    if(c=='[') { c='A'; numberafter++; }
    sprintf(dump,"%cx%d",c,numberafter);
    c++;
    write(fd[1], dump, (strlen(dump)));    
    i++;
      }
      close(fd[1]);
      exit(0);
    }

  //Process A
  int x = 0;
  close(fd[1]);
  if(childpidB > 0 && childpidC > 0) {
    while ((nbytes = read(fd[0], readbuffer, sizeof(readbuffer))) > 0) {
      if(x%50==0 && x!=0) {
    usleep(200);
      }
      printf("%s\n", readbuffer);
      x++;
    }
  }
  close(fd[0]);
  exit(0);
}

Upvotes: 0

Views: 2031

Answers (1)

GreenScape
GreenScape

Reputation: 7737

The problem is in nulls that you are sending to the parent:

write(fd[1], numbers, (strlen(numbers)+1));
...
write(fd[1], dump, (strlen(dump)+1)); 

Both writes will include null character because of +1 in the expression after strlen(), hence

printf("%s\n", readbuffer);

will stop outputting data from the readbuffer after first null byte.

Parent actually does receive 100 bytes (or less, when both childs are out).

So, do not send nulls, by removing +1 ;)

Edit:

It seems like childpidC will sleep longer (4 * 300 = 1200 us vs 4 * 100 = 400 us). So when childpidB exits first, OS sends SIGCHLD to the parent, to notify it about the one of the childs exit. At very same moment read() will be interrupded and may return less than sizeof(readbuffer) or even -1 (with an error EINTR).

Here is an example function for non-interruptible read from pipe:

ssize_t read_no_intr(int pipe_fd, void * buffer, size_t size)
{
    if (!size)
        return 0;

    ssize_t bytes_read = 0;
    ssize_t batch;
    char * p_buffer = static_cast<char*>(buffer);

    while ((size_t)bytes_read < size) {
        batch = ::read(pipe_fd, p_buffer, size - bytes_read, 0);
        if (batch == -1) {
            if(errno == EINTR) {
                // Interrupted by a signal, retry.
                continue;
            } else if (!bytes_read) {
                // An error, and no bytes were read so far.
                return -1;
            } else {
                // An error, but something was read.
                return bytes_read;
            }
        } else if(!batch) {
            // 0 means end of a stream.
            break;
        }
        bytes_read += batch;
        p_buffer += batch;
    }
    return bytes_read;
}

Upvotes: 2

Related Questions