DEV Community

Gealber Morales
Gealber Morales

Posted on • Edited on • Originally published at gealber.com

Pipes II

Pipes II

Introduction

In this part of the tutorial we will be creating and using a pipe, wish me luck 😅, don't worry we can do it.

Prerequisites

  1. Been on a Linux OS.
  2. A C/C++ compiler to compile a code of more than 10 lines😬.

Creating a pipe in C

Now how do we create a pipe with code? Mmm 🤔, here's a snippet.

#include <unistd.h>

int main(void) {
    //file descriptors
    int pipefd[2];
    int pipe(pipefd);
}
Enter fullscreen mode Exit fullscreen mode

Let's see part of the documentation of the pipe() on the manpage:

pipe() creates a pipe, a unidirectional data channel that can be used for interprocess communication. The array pipefd is used to return two file descriptors referring to the ends of the pipe. pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the write end of the pipe.

This argument that takes contains the file descriptors needed, one for the read end of the pipe and another for the write end of the pipe. So ideally with one you push the data on the pipe and with the other you access that data. Very ingenious, right?

Let's see if we could do something with this, I will tested on a very simple program the will behave like this:

  • There's going to be two process.
  • One process will write sequentially the numbers from 0 to 9, waiting one second on each write.
  • The other process will read the data from the pipe, and it will printed on the console.

I recommend you to play around with this by yourself. I mean you could try exactly as I put it here but you would understand better if you create a simple example by yourself. This steps will help you also:

  • Watch first this example, learn from it.
  • Try to explain it to someone else.
  • Get your hands dirty with code.

Let's see the code and break it in pieces, it would be better that way, because you know, it is C 🙄 .

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
  // file descriptors to use on the write end
  // and read end of the pipe
  int filedes[2];

  if (pipe(filedes) == -1) {
    perror("pipe couldn't be created");
    return -1;
  }
  // after this both file descriptors are open

  // creating a child process
  pid_t process = fork();
  switch (process) {
  case -1:
    perror("fork failed");
    return -1;

  // we are in the child process
  case 0:
    // closing write end pipe in child
    if (close(filedes[1]) == -1) {
      perror("closing in child");
      return -1;
    }
    break;

  // we are in the parent process
  default:
    if (close(filedes[0]) == -1) {
      perror("closing in parent");
      return -1;
    } // closing read end pipe in parent
  }

  if (passingMsg(process, filedes) == -1) {
    perror("passingMsg");
    return -1;
  }

  // cleaning
  if (process == 0)
    close(filedes[0]);
  else
    close(filedes[1]);

  return 0;
}

Enter fullscreen mode Exit fullscreen mode

In this first snippet we can see that I made use of fork() to create a new process, go to its manpage, with man fork and you will see its documentation. If the documentation is too heavy don't worry I got your back, this 👉 tutorial from Geeks for Geeks, is precisely about fork(), take a look at it is very concise.

If you read the code you notice that I created a pipe, a new child process with fork() and then I made some magic there on the switch statement, right? What just happened there? Let me draw you the situation:

After the creation of the pipe

    +-[Calling process]<-+   f0: read end of the pipe
    | f1          f0     |   f1: write end of the pipe
    |                    |
    +--->| Pipe  |-------+
Enter fullscreen mode Exit fullscreen mode

This is with a single process because we haven't used fork() yet both file descriptors are on the calling process, we will call it parent process.

After the creation of the new process

    +-[Calling process]<-+   f0: read end of the pipe
    | f1          f0     |   f1: write end of the pipe
    |      _______       |
    \+--->|       |-----+/
          | Pipe  |
    /+--->|_______|-----+\
    | f1          f0     |
    |                    |
    +---[Child process]<-+
Enter fullscreen mode Exit fullscreen mode

Now here is the trick, after the creation of the child process, both processes can write and read from the pipe. Which may be the behavior needed by your application but in our case, I want to write from the parent or calling process and read from the child process. That's the reason why I closed on the switch statement first the write end file descriptor of the child process, and later also closed the read end file descriptor on the parent process. It seems like a tongue twister, but my next draw will help you understand:

After the switch statement

    +-[Calling process]      f0: read end of the pipe
    | f1                   f1: write end of the pipe
    |      _______       
    \+--->|       |
          | Pipe  |
          |_______|-----+\
                  f0     |
                         |
        [Child process]<-+
Enter fullscreen mode Exit fullscreen mode

After the switch statement I'm ready to pass messages from the parent to the child, here is the part of the code corresponding to the function passingMsg.

int passingMsg(pid_t process, int *filedes) {
  char buff[3];
  int BUF_SIZE = 3;
  int bRead;
  char msg[3];

  switch (process) {
  //child process
  case 0:
    printf("MSG From Parent:\n");
    for (;;) {
      bRead = read(filedes[0], buff, BUF_SIZE); //reading from the pipe
      if (bRead == -1) {
        perror("read on child");
        return -1;
      }
      if (bRead == 0) // end of steam data
        break;
      // writing msg to stdout
      if (write(STDOUT_FILENO, buff, bRead) != bRead) { //writing to the console
        perror("write to stdout");
        return -1;
      }
    }
    break;
  //parent process
  default:
    for (int i = 0; i < 10; i++) {
      sleep(1);
      snprintf(msg, 3, "%d\n", i);
      if (write(filedes[1], msg, strlen(msg)) == -1) { //writing on the pipe
        perror("write from parrent");
        return -1;
      }
    }
  }
  return 0;
}
Enter fullscreen mode Exit fullscreen mode

What about other language apart from C

I will leave you two links of pipes in Go and Python.

Python:

Go:

No idea how is this for other programming languages go and find how are pipes on other programming language that you like 😃.

Conclusion

Well it is a pretty long article I admit it, but if you read it with calm you will learn a cool stuff. For me this is also new so don't worry if you don't understand it at first. There's a lot of information about pipe.

That's all folks 👋.

Bibliography

  1. The Linux Programming Interface by Michael Kerrisk.
  2. fork() in C
  3. manpage for fork()

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.