商城首页欢迎来到中国正版软件门户

您的位置:首页 >C++在Linux下如何进行进程间通信

C++在Linux下如何进行进程间通信

  发布于2026-04-26 阅读(0)

扫一扫,手机访问

C++在Linux下如何进行进程间通信

C++在Linux下如何进行进程间通信

在Linux环境下开发C++程序,进程间通信(IPC)是个绕不开的话题。当多个进程需要协同工作、交换数据时,Linux系统提供了多种成熟的方案。每种方案都有其独特的“性格”和适用场景,理解它们的差异,是写出高效、稳定程序的关键。接下来,我们就逐一拆解这些常用的IPC方法。

1. 管道(Pipes)

管道算得上是Linux IPC的“元老”了,它提供了一种半双工的通信方式。什么叫半双工?简单说,就是数据只能单向流动。这种机制在父子进程之间尤其好用,因为它直接继承了文件描述符,创建和使用都非常直观。

无名管道(Anonymous Pipes)

无名管道,顾名思义,它没有名字,生命周期随进程结束而终结。它最大的特点就是只能在具有亲缘关系的进程(比如父子进程)间使用。其工作原理是:先调用pipe()系统调用创建一个管道,这会返回两个文件描述符,一个用于读,一个用于写。父进程fork()出子进程后,双方各自关闭不需要的一端,就能建立起一条单向的数据通道。

#include 
#include 
#include 
#include 

int main() {
    int pipefd[2];
    pid_t pid;
    char buffer[256];

    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) { // 子进程
        close(pipefd[1]); // 关闭写端
        read(pipefd[0], buffer, sizeof(buffer));
        std::cout << "Child received: " << buffer << std::endl;
        close(pipefd[0]);
    } else { // 父进程
        close(pipefd[0]); // 关闭读端
        const char* message = "Hello from parent";
        write(pipefd[1], message, strlen(message) + 1);
        close(pipefd[1]);
        wait(NULL); // 等待子进程结束
    }
    return 0;
}

命名管道(Named Pipes, FIFOs)

无名管道好用,但限制也明显——只能用于亲缘进程。这时候,命名管道(FIFO)就派上用场了。它在文件系统中有一个路径名,任何知道这个路径的进程都可以打开它进行读写,完全解除了亲缘关系的束缚。你可以把它想象成一个有名字的、先进先出的特殊文件。

#include 
#include 
#include 
#include 
#include 

int main() {
    const char* fifo_name = "/tmp/myfifo";
    int fd;
    char buffer[256];

    // 创建命名管道
    if (mkfifo(fifo_name, 0666) == -1) {
        perror("mkfifo");
        exit(EXIT_FAILURE);
    }

    fd = open(fifo_name, O_RDWR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 写入数据
    const char* message = "Hello from FIFO";
    write(fd, message, strlen(message) + 1);

    // 读取数据
    read(fd, buffer, sizeof(buffer));
    std::cout << "Received: " << buffer << std::endl;

    close(fd);
    unlink(fifo_name); // 删除命名管道
    return 0;
}

2. 消息队列(Message Queues)

如果说管道是字节流,那么消息队列就是“快递柜”。它允许进程以结构化的消息为单位进行数据交换,每条消息都有类型标识。发送方和接收方可以基于消息类型进行选择性接收,这提供了比管道更灵活的通信模式。消息队列独立于进程存在,即使通信进程结束了,消息仍然可以保留在队列中。

#include 
#include 
#include 
#include 
#include 

struct msg_buffer {
    long mtype;
    char mtext[100];
};

int main() {
    key_t key = ftok("msgqueue_example.c", 65);
    int msgid = msgget(key, 0666 | IPC_CREAT);
    if (msgid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }

    msg_buffer buffer;
    buffer.mtype = 1;
    strcpy(buffer.mtext, "Hello from message queue");

    // 发送消息
    if (msgsnd(msgid, &buffer, sizeof(buffer.mtext), 0) == -1) {
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }

    // 接收消息
    if (msgrcv(msgid, &buffer, sizeof(buffer.mtext), 1, 0) == -1) {
        perror("msgrcv");
        exit(EXIT_FAILURE);
    }

    std::cout << "Received message: " << buffer.mtext << std::endl;
    msgctl(msgid, IPC_RMID, NULL); // 删除消息队列
    return 0;
}

3. 共享内存(Shared Memory)

追求极致性能?那共享内存绝对是你的首选。它的原理很直接:在内存中划出一块区域,让多个进程都能映射到自己的地址空间。这样一来,进程间的数据交换就变成了直接对内存的读写,完全避免了内核缓冲区的数据拷贝,速度自然是最快的。但天下没有免费的午餐,共享内存不提供任何同步机制,需要配合信号量等工具来防止数据竞争。

#include 
#include 
#include 
#include 
#include 

int main() {
    key_t key = ftok("shm_example.c", 65);
    int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    char* str = (char*) shmat(shmid, (void*)0, 0);
    if (str == (char*)(-1)) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    strcpy(str, "Hello from shared memory");
    std::cout << "Message written in memory: " << str << std::endl;

    shmdt(str); // 分离共享内存
    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

4. 信号量(Semaphores)

严格来说,信号量本身并不是为了传递数据,而是为了解决一个更根本的问题:进程同步。当多个进程需要访问同一块共享资源(比如刚提到的共享内存)时,如何避免冲突?信号量就像一个“资源计数器”或“通行证”。最常见的二元信号量(互斥锁)可以确保同一时刻只有一个进程能进入临界区。P操作(等待)尝试获取信号量,V操作(发信号)则释放信号量。

#include 
#include 
#include 
#include 
#include 

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

int main() {
    key_t key = ftok("semaphore_example.c", 65);
    int semid = semget(key, 1, 0666 | IPC_CREAT);
    if (semid == -1) {
        perror("semget");
        exit(EXIT_FAILURE);
    }

    union semun arg;
    arg.val = 1; // 初始化信号量为1
    if (semctl(semid, 0, SETVAL, arg) == -1) {
        perror("semctl");
        exit(EXIT_FAILURE);
    }

    // P操作(等待信号量)
    struct sembuf sb = {0, -1, SEM_UNDO};
    if (semop(semid, &sb, 1) == -1) {
        perror("semop");
        exit(EXIT_FAILURE);
    }
    std::cout << "Semaphore P operation completed" << std::endl;

    // V操作(释放信号量)
    sb.sem_op = 1;
    if (semop(semid, &sb, 1) == -1) {
        perror("semop");
        exit(EXIT_FAILURE);
    }
    std::cout << "Semaphore V operation completed" << std::endl;

    semctl(semid, 0, IPC_RMID); // 删除信号量
    return 0;
}

5. 套接字(Sockets)

最后登场的是功能最强大的套接字。它最初是为网络通信设计的,但同样完美适用于同一台主机上的进程间通信(通过Unix Domain Sockets)。套接字提供全双工通信,支持多种协议(如TCP的可靠流、UDP的数据报),并且是跨平台兼容性最好的IPC方式之一。如果未来你的程序有扩展到网络通信的可能,那么从一开始就使用套接字会是一个具有前瞻性的选择。

Unix Domain Sockets

这是专门用于本地IPC的套接字,它通过文件系统路径名来标识,效率比网络套接字更高,因为它避免了网络协议栈的开销。

#include 
#include 
#include 
#include 
#include 
#include 

int main() {
    struct sockaddr_un addr;
    int sockfd, connfd;
    char buffer[1024];

    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, "/tmp/unix_socket", sizeof(addr.sun_path) - 1);
    unlink("/tmp/unix_socket"); // 删除已存在的套接字文件

    if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    if (listen(sockfd, 5) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    connfd = accept(sockfd, NULL, NULL);
    if (connfd == -1) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    read(connfd, buffer, sizeof(buffer));
    std::cout << "Received: " << buffer << std::endl;

    close(connfd);
    close(sockfd);
    return 0;
}

好了,以上就是Linux下C++常用的几种进程间通信方法。简单总结一下:管道简单直接,适合亲缘进程;命名管道打破了亲缘限制;消息队列提供了结构化的消息传递;共享内存速度最快,但需手动同步;信号量是同步的基石;而套接字则是最通用、最强大的方案。实际开发中,没有绝对的“最佳”,只有最“合适”。理解它们的原理和特性,根据你的具体场景——是要求速度、简便性,还是未来的可扩展性——来做出选择,这才是关键所在。

本文转载于:https://www.yisu.com/ask/25595955.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注