Declaration: This is not AI Slop. Real human (me) wrote this. But tools including AI were used for research and learning.
Declaration: Pipes are not network programming. I just happen to start learning low level coding from pipes and hence documenting it.
When you want to transfer data between two processes, things get interesting. Operating systems doesn't allow processes to take data from another process directly, even if the two processes were of the same program, and this is a good thing. This process isolation ensures that for e.g., some image editing software running with chrome wont be able to snatch out the amazon payment you are doing in chrome. But this makes the lives of legit developers bit difficult, doesn't it ? So instead of directly accessing the insides of that running process, you and the developer of that process can agree upon what information to share and use standard tools to make everything work. This is blog 1 of learning Client server computing/network programming and today's topic is pipes.
Need to make one thing clear - programming using pipes are not network programming, its just that network programming is what I'm trying to learn and I happen to start with pipes.
Pipes can be used in the terminal and in programs to transfer data between 2 processes. It is possible to transfer data between processes of different programs when using pipes in terminal, you are probably already doing it like this
$ ls /bin | grep system | more
But in your programs, you can only use pipes for exchanging data as long as the sender and receiver are forks of the same process only. Before showing code, I will explain why.
The handles to these pipes, called anonymous pipes only exist as file descriptors in the memory space of the process that created it. A second separate process cannot access the first one's memory space to get a hold of it. So, these cannot be used for information exchange between two different processes of different programs.
#include <stdio.h>
#include <unistd.h>
#include <err.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int pipefd[2];
int child_pid;
char *payload = "Hello pipes\0";
char child_buffer;
char child_payload[12];
if (pipe(pipefd) == -1)
{
err(EXIT_FAILURE, "pipe");
}
child_pid = fork();
if (child_pid == -1)
{
err(EXIT_FAILURE, "fork");
}
if (child_pid == 0)
// child_pid == 0 means it is child.
// for parent child_pid will have the child's actual non zero process id
{
memset(child_payload, 0, sizeof(child_payload));
close(pipefd[1]);
printf("Reading payload from parent...\n");
while (read(pipefd[0], &child_buffer, 1) > 0)
{
strncat(child_payload, &child_buffer, 1);
}
printf("%s\n", child_payload);
close(pipefd[0]);
exit(EXIT_SUCCESS);
}
else
{
// we are going to write a message to child using
// the write fd of the pipe (i.e, the one on index 1)
// the one in index 0 is read end of the pipe
close(pipefd[0]);
printf("Writing payload to child process...\n");
if (write(pipefd[1], payload, strlen(payload)) == -1)
{
err(EXIT_FAILURE, "write");
}
if (close(pipefd[1]) == -1)
{
err(EXIT_FAILURE, "close");
}
if (wait(NULL) == -1) /* Wait for child to exit */
{
err(EXIT_FAILURE, "wait");
}
exit(EXIT_SUCCESS);
}
return 0;
}
The above program forks to create 2 processes of the same program and then transfers the string "Hello pipes" from parent process to child. (If you don't understand what is a file descriptor, feel free to comment and I will try to write a blog post for it.) The program starts, then creates a pipe. The created pipe will always have 2 ends - a read end and a write end. These ends are given to the programmer as an array of 2 integer file descriptors. The first file descriptor is the read end, and the second file descriptor is the write end. Then we fork() to create a child process. The parent is the one who needs to give out data and child is the one to accept the data. This means, parent only needs to use the write fd of the pipe and child only needs to use the read fd of the pipe. Parent can safely close the read end and child can safely close the write end as they wont be using it. It is important that parent and child close the file descriptor they are not using - otherwise the program will deadlock because of [insert complex technical reason here]. After this, the parent can safely write() to the file descriptor representing the write end of the pipe and the child can read() the read end. The parent waits for the child to exit and then it itself exits.
If you would like to compile my code, save my code as main.c and run the following command in a Linux bash shell
$ gcc -o main main.c
$ ./main
Criticisms and discussions welcome..! Hope I made your brain's networking side a little bit better.
Top comments (2)
Nice work. What does a file descriptor mean?
when a process wants to access literally anything in the system, like a file, or send a network request or use a device, it needs to request the operating system through syscalls. If the OS decides your process can have the requested access, you will get an integer back from that syscall. This integer is called file descriptor.