Inter-Process Communication in C: Pipes and Message Queues

Inter-Process Communication in C: Pipes and Message Queues

In operating systems, a process is the basic unit of resource allocation, and inter-process communication (IPC) refers to the mechanisms that allow different processes to exchange data and information. The C language provides several ways to implement IPC, with the two most commonly used methods being pipes and message queues. This article will detail these two methods and demonstrate them with code examples.

1. Pipes

1. Overview of Pipes

A pipe is a half-duplex communication mechanism that allows one process to send data to another process. It is typically used for data transfer between parent and child processes. When using a pipe, one end writes data while the other end reads data.

2. Creating a Pipe

In C, a pipe can be created using the <span>pipe()</span> function. This function takes an integer array as a parameter to store the read and write file descriptors.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
    int fd[2]; // File descriptor array
    if (pipe(fd) == -1) { // Create pipe
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    // fd[0] is the read end, fd[1] is the write end
    return 0;
}

3. Usage Example

The following is a simple example demonstrating how to use a pipe for data transfer between a parent and child process:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
    int fd[2];
    pid_t pid;
    if (pipe(fd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    pid = fork(); // Create child process
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    if (pid > 0) { // Parent process
        close(fd[0]); // Close read end
        const char *msg = "Hello from parent!";
        write(fd[1], msg, sizeof(msg)); // Send message to write end
        close(fd[1]); // Close write end
        wait(NULL);   // Wait for child process to finish
    } else { // Child process
        close(fd[1]); // Close write end
        char buffer[100];
        read(fd[0], buffer, sizeof(buffer)); // Receive message from read end
        printf("Child received: %s\n", buffer);
        close(fd[0]); // Close read end
    }
    return 0;
}

Summary

In the above code, we first created a pipe and then used the <span>fork()</span> function to create a child process. In the parent process, we sent a message to the write file descriptor, while in the child process, we received and printed the message from the read file descriptor.

2. Message Queues

1. Overview of Message Queues

Unlike pipes, message queues are a more complex but powerful IPC mechanism that allows multiple producers and consumers to exchange information asynchronously. Each message has its own priority and can be processed in order.

2. Creating and Using Message Queues

To use message queues, you need to include the header files <span><sys/ipc.h></span> and <span><sys/msg.h></span>. You can create or access a new or existing message queue using the <span>msgget()</span> function.

Message Structure Definition:

struct msgbuf {
   long mtype;       /* message type */
   char mtext[100]; /* message text */
};

Example Code:

The following is an example of how to create and use a simple message queue for communication:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
struct msgbuf {
   long mtype;       /* message type */
   char mtext[100]; /* message text */
};
int main() {
   key_t key = ftok("progfile",65);
   int msgid = msgget(key,0666|IPC_CREAT);
   struct msgbuf message;
   pid_t pid = fork();
   if (pid > 0) {
        message.mtype = 1;
        strcpy(message.mtext, "Hello from parent!");
        msgsnd(msgid,&message,sizeof(message),0);
        printf("Parent sent: %s\n", message.mtext);
        wait(NULL);
   } else {
        msgrcv(msgid,&message,sizeof(message),1,0);
        printf("Child received: %s\n", message.mtext);
        msgctl(msgid, IPC_RMID,NULL);
   }
   return 0;
}

Summary

In this example, we first generated a unique key and created a new message queue. In the parent process, we constructed a new message with a type identifier of <span>mtype</span> and sent it to the queue. In the child process, we received the message of that specific type and printed it. Finally, we deleted the message queue using the <span>msgctl()</span> function to free resources.

3. Summary Comparison

  • Applicable Scenarios:

    • Pipes are suitable for simple one-to-one communication.
    • Message queues exhibit higher efficiency and flexibility, making them more suitable for complex application scenarios.
  • Synchronization:

    • Pipes provide blocking communication.
    • Message queues can be set to non-blocking mode to improve efficiency.
  • Performance:

    • For small data transfers, both methods perform similarly, but as the load increases, choosing the appropriate method becomes particularly important.

I hope this article helps you understand the two main IPC mechanisms in C—pipes and message queues—and enables you to apply this knowledge to solve practical problems.

Leave a Comment