Inter-Process Communication (IPC) in C++

In C++, Inter-Process Communication (IPC) is a mechanism that allows multiple independent processes to exchange data and coordinate operations. Below are detailed descriptions of three common IPC methods:

1. Pipes

Pipes are a half-duplex communication method where data can only flow in one direction, and they are divided into anonymous pipes and named pipes.

Anonymous Pipes

  • Characteristics:

  • Can only be used between parent and child processes or sibling processes (related processes).

  • One-way communication, one end reads, and the other end writes.

  • Lifecycle is destroyed when the process ends.

  • Principle:

  • Created through the system call pipe(int fd[2]), which returns two file descriptors: fd[0] (read end) and fd[1] (write end).

  • Data written to the pipe is stored in the kernel buffer and is removed upon reading.

  • Example Code (Parent-Child Process Communication):

#include <unistd.h>
#include <iostream>
int main() {
    int fd[2];
    char buffer[100];
    if (pipe(fd) == -1) {
        perror("pipe failed");
        return 1;
    }
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork failed");
        return 1;
    }
    if (pid == 0) {  // Child process: write data
        close(fd[0]);  // Close read end
        const char* msg = "Hello from child!";
        write(fd[1], msg, strlen(msg) + 1);
        close(fd[1]);
    } else {  // Parent process: read data
        close(fd[1]);  // Close write end
        read(fd[0], buffer, sizeof(buffer));
        std::cout << "Parent received: " << buffer << std::endl;
        close(fd[0]);
    }
    return 0;
}

Named Pipes (FIFOs)

  • Characteristics:

  • Exist as files (created using mkfifo), can be used by unrelated processes.

  • Follow the first-in, first-out (FIFO) principle.

  • Processes read and write by opening the file.

  • Example Code:

// Writer Process
#include <fcntl.h>
#include <unistd.h>
int main() {
    int fd = open("myfifo", O_WRONLY);
    write(fd, "Hello from writer!", 18);
    close(fd);
    return 0;
}
// Reader Process
#include <fcntl.h>
#include <iostream>
#include <unistd.h>
int main() {
    int fd = open("myfifo", O_RDONLY);
    char buffer[100];
    read(fd, buffer, 100);
    std::cout << "Reader received: " << buffer << std::endl;
    close(fd);
    return 0;
}

Note: FIFO file must be created first: mkfifo myfifo.

2. Shared Memory

Shared memory allows multiple processes to directly access the same physical memory, making it the fastest IPC method.

  • Characteristics:

  • Efficient: Data does not need to be copied, accessed directly via pointers.

  • Requires synchronization: Must use semaphores or mutexes to avoid race conditions.

  • Lifecycle: Independent of processes, must be manually deleted.

  • Principle:

  • Create a shared memory segment (shmget).

  • Map it to the process’s address space (shmat).

  • Processes read and write memory, then unmap (shmdt) and delete (shmctl) after operations are complete.

  • Example Code:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <iostream>
#include <cstring>
int main() {
    // Create shared memory segment (key 1234, size 1024 bytes)
    int shmid = shmget(1234, 1024, 0666 | IPC_CREAT);
    if (shmid == -1) {
        perror("shmget failed");
        return 1;
    }
    // Map to current process
    char* shared_memory = (char*)shmat(shmid, nullptr, 0);
    if (shared_memory == (char*)-1) {
        perror("shmat failed");
        return 1;
    }
    // Write data
    std::strcpy(shared_memory, "Hello from shared memory!");
    // Unmap
    if (shmdt(shared_memory) == -1) {
        perror("shmdt failed");
        return 1;
    }
    // Reading process is similar, just change the write operation to read
    return 0;
}

3. Message Queues

Message queues are a linked list managed by the kernel, allowing processes to communicate by sending/receiving messages.

  • Characteristics:

  • Asynchronous communication: The sender and receiver do not need to run simultaneously.

  • Message types: Messages can be categorized by type, allowing the receiver to select the type to receive.

  • Persistence: Messages remain until read or the queue is deleted.

  • Principle:

  • Create a message queue (msgget).

  • Send messages (msgsnd) and receive messages (msgrcv).

  • Delete the queue (msgctl).

  • Example Code:

#include <sys/msg.h>
#include <iostream>
#include <cstring>
struct Message {
    long mtype;       // Message type (must be greater than 0)
    char mtext[100];  // Message content
};
int main() {
    // Create message queue (key 5678)
    int msgid = msgget(5678, 0666 | IPC_CREAT);
    if (msgid == -1) {
        perror("msgget failed");
        return 1;
    }
    // Send message
    Message msg;
    msg.mtype = 1;
    std::strcpy(msg.mtext, "Hello from message queue!");
    if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) {
        perror("msgsnd failed");
        return 1;
    }
    // Receive message
    Message received;
    if (msgrcv(msgid, &received, sizeof(received.mtext), 1, 0) == -1) {
        perror("msgrcv failed");
        return 1;
    }
    std::cout << "Received: " << received.mtext << std::endl;
    // Delete queue
    if (msgctl(msgid, IPC_RMID, nullptr) == -1) {
        perror("msgctl failed");
        return 1;
    }
    return 0;
}

4. Comparison and Selection

Method

Advantages

Disadvantages

Applicable Scenarios

Pipes

Simple to use, automatic synchronization

One-way, limited capacity

Small data transfer between parent and child processes

Shared Memory

Fastest speed, suitable for large data

Requires manual synchronization, complex management

High-performance computing, graphics processing

Message Queues

Asynchronous communication, categorized by type

Less efficient than shared memory

Distributed systems, event-driven architectures

Considerations

  1. Synchronization Issues: Shared memory and pipes need to be aware of race conditions; it is recommended to use semaphores or mutexes.

  2. Resource Release: Shared memory and message queues need to be manually deleted to avoid memory leaks.

  3. Platform Differences: IPC implementations differ between Windows and Linux; the above examples are suitable for POSIX systems.

Leave a Comment