学懂C++(三十六):深入理解与实现C++进程间通信(IPC)

目录

一、概念与原理

二、IPC的核心点

三、进程间通信及经典实例详解

1、管道(Pipes)

概念与原理

核心点

实现实例

2、消息队列(Message Queues)

概念与原理

核心点

实现实例

3、共享内存(Shared Memory)

概念与原理

核心点

实现实例

4、信号(Signals)

概念与原理

核心点

实现实例

5、套接字(Sockets)

概念与原理

核心点

实现实例

四、总结


一、概念与原理

进程间通信(IPC)的概念: IPC是指在同一主机的不同进程之间传递数据和信号的机制。它允许多个进程共享数据和资源,实现协同工作。IPC的主要技术有管道(Pipes)、消息队列(Message Queues)、共享内存(Shared Memory)、信号(Signals)、套接字(Sockets)等。

IPC的原理: IPC的基本原理是通过操作系统提供的机制,在不同进程之间传递数据和信号。操作系统提供了专用的API和系统调用来实现这些功能。每种IPC机制都有其优点和适用场景,选择合适的IPC技术取决于具体的需求。

二、IPC的核心点

  1. 管道(Pipes)

    • 概念:管道是一种单向通信机制,数据从一端(写端)流向另一端(读端)。
    • 实现方式:使用系统调用pipe创建管道。
    • 适用场景:适用于父子进程之间的通信。
  2. 命名管道(Named Pipes/FIFOs)

    • 概念:命名管道是管道的扩展,允许无亲缘关系的进程通信。
    • 实现方式:使用系统调用mkfifo创建命名管道。
    • 适用场景:适用于任意两个进程之间的通信。
  3. 消息队列(Message Queues)

    • 概念:消息队列是一个存放消息的数据结构,允许进程以消息的形式传递数据。
    • 实现方式:使用System V IPC或POSIX消息队列API。
    • 适用场景:适用于需要消息传递和排队处理的场景。
  4. 共享内存(Shared Memory)

    • 概念:共享内存允许多个进程在同一块物理内存区域中读写数据。
    • 实现方式:使用System V共享内存或POSIX共享内存API。
    • 适用场景:适用于需要高效率大数据量传输的场景。
  5. 信号(Signals)

    • 概念:信号是一种进程间传递通知的机制。
    • 实现方式:使用系统调用kill发送信号,信号处理函数处理信号。
    • 适用场景:适用于异步事件通知。
  6. 套接字(Sockets)

    • 概念:套接字是一种通用的通信机制,支持本地和网络通信。
    • 实现方式:使用BSD套接字API。
    • 适用场景:适用于网络和本地进程之间的通信。

三、进程间通信及经典实例详解

1、管道(Pipes)

概念与原理

管道是一种单向通信机制,主要用于父子进程之间的数据传输。管道创建后,数据从一端(写端)流向另一端(读端)。管道是通过操作系统内核缓冲区实现的,具有缓冲区限制,超出限制后写操作会阻塞直到有空间。

核心点
  • 创建管道:使用pipe系统调用创建。
  • 读写管道:分别使用文件描述符pipefd[0]pipefd[1]
  • 关闭未使用端:避免资源浪费和潜在的死锁。
实现实例
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring> // for strlen

int main() {
    int pipefd[2]; // 存储管道的文件描述符
    pid_t cpid;
    char buf;

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

    cpid = fork(); // 创建子进程
    if (cpid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (cpid == 0) {    // 子进程
        close(pipefd[1]); // 关闭未使用的写端

        std::cout << "子进程从管道读取数据:\n";
        while (read(pipefd[0], &buf, 1) > 0) { // 从管道读数据
            write(STDOUT_FILENO, &buf, 1); // 将数据写到标准输出
        }

        write(STDOUT_FILENO, "\n", 1);
        close(pipefd[0]); // 关闭读端
        _exit(EXIT_SUCCESS);

    } else {            // 父进程
        close(pipefd[0]); // 关闭未使用的读端
        const char* msg = "来自父进程的消息!\n";
        write(pipefd[1], msg, strlen(msg)); // 将消息写入管道
        close(pipefd[1]); // 关闭写端,使子进程看到EOF
        wait(NULL); // 等待子进程结束
        exit(EXIT_SUCCESS);
    }
}

解析

  • pipe(pipefd):创建一个管道,pipefd[0]用于读,pipefd[1]用于写。
  • fork():创建一个子进程。
  • 子进程:关闭写端,读取数据并输出到标准输出。
  • 父进程:关闭读端,写入数据到管道,然后等待子进程结束。

运行结果

子进程从管道读取数据:
来自父进程的消息!

2、消息队列(Message Queues)

概念与原理

消息队列是一种存储消息的数据结构,允许进程以消息的形式传递数据。不同进程可以向消息队列发送和接收消息,具有多个发送者和接收者。消息队列可以实现消息的优先级排序。

核心点
  • 创建消息队列:使用msgget系统调用创建。
  • 发送消息:使用msgsnd系统调用。
  • 接收消息:使用msgrcv系统调用。
  • 删除消息队列:使用msgctl系统调用。
实现实例
#include <iostream>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>

// 定义消息结构体
struct message {
    long msg_type;
    char msg_text[100];
};

int main() {
    key_t key = ftok("progfile", 65);  // 生成唯一的键值
    int msgid = msgget(key, 0666 | IPC_CREAT);  // 创建消息队列
    message msg;

    if (fork() == 0) { // 子进程
        msg.msg_type = 1; // 设置消息类型
        strcpy(msg.msg_text, "来自子进程的消息");
        msgsnd(msgid, &msg, sizeof(msg), 0); // 发送消息到消息队列
        std::cout << "子进程发送消息: " << msg.msg_text << std::endl;
    } else { // 父进程
        wait(NULL); // 等待子进程结束
        msgrcv(msgid, &msg, sizeof(msg), 1, 0); // 从消息队列接收消息
        std::cout << "父进程接收到消息: " << msg.msg_text << std::endl;
        msgctl(msgid, IPC_RMID, NULL); // 删除消息队列
    }

    return 0;
}

解析

  • ftok("progfile", 65):生成一个唯一键。
  • msgget(key, 0666 | IPC_CREAT):创建消息队列。
  • 子进程:设置消息类型,发送消息到消息队列。
  • 父进程:等待子进程结束,从消息队列接收消息,然后删除消息队列。

运行结果

子进程发送消息: 来自子进程的消息
父进程接收到消息: 来自子进程的消息

3、共享内存(Shared Memory)

概念与原理

共享内存是一种高效的进程间通信机制,允许多个进程直接访问同一块物理内存。共享内存是所有IPC机制中最快的,因为数据不需要在内核和用户空间之间进行复制。

核心点
  • 创建共享内存:使用shmget系统调用创建。
  • 附加共享内存:使用shmat系统调用将共享内存附加到进程地址空间。
  • 分离共享内存:使用shmdt系统调用分离共享内存。
  • 删除共享内存:使用shmctl系统调用删除共享内存。
实现实例
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <cstring>
#include <unistd.h>

#define SHM_SIZE 1024  // 共享内存大小

int main() {
    key_t key = ftok("shmfile", 65);  // 生成唯一键
    int shmid = shmget(key, SHM_SIZE, 0666|IPC_CREAT);  // 创建共享内存段
    char *str = (char*) shmat(shmid, nullptr, 0);  // 附加到共享内存

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

    if (cpid == 0) {    // 子进程
        sleep(1);  // 确保父进程先写入
        std::cout << "子进程从共享内存读取数据:\n";
        std::cout << str << std::endl;
        shmdt(str);  // 分离共享内存
        _exit(EXIT_SUCCESS);
    } else {            // 父进程
        std::cout << "父进程写入共享内存:\n";
        const char* msg = "来自父进程的消息";
        strncpy(str, msg, SHM_SIZE);
        wait(NULL);  // 等待子进程
        shmdt(str);  // 分离共享内存
        shmctl(shmid, IPC_RMID, nullptr);  // 删除共享内存
        exit(EXIT_SUCCESS);
    }
}

解析

  • ftok("shmfile", 65):生成一个唯一键。
  • shmget(key, SHM_SIZE, 0666 | IPC_CREAT):创建共享内存段。
  • shmat(shmid, NULL, 0):将共享内存段附加到进程地址空间。
  • 子进程:等待父进程将数据写入共享内存,从共享内存读取数据并显示。
  • 父进程:将数据写入共享内存,等待子进程结束,分离并删除共享内存段。

运行结果

父进程写入共享内存:
子进程从共享内存读取数据:
来自父进程的消息

4、信号(Signals)

概念与原理

信号是一种用于进程间传递通知的机制。信号是一种异步通信方式,用于通知进程发生了某个事件。每个信号都有一个默认的处理动作,但进程可以定义自己的信号处理函数。

核心点
  • 发送信号:使用kill系统调用。
  • 处理信号:定义信号处理函数,并使用signalsigaction系统调用安装信号处理函数。
  • 信号屏蔽:使用sigprocmask屏蔽和解除屏蔽信号。
实现实例
#include <iostream>
#include <csignal>
#include <unistd.h>

// 信号处理函数
void signalHandler(int signum) {
    std::cout << "收到信号: " << signum << std::endl;
    exit(signum);
}

int main() {
    signal(SIGINT, signalHandler);  // 安装信号处理函数

    pid_t cpid = fork();
    if (cpid == 0) { // 子进程
        sleep(2); // 等待2秒
        kill(getppid(), SIGINT); // 向父进程发送SIGINT信号
        _exit(0);
    } else { // 父进程
        std::cout << "父进程等待信号...\n";
        pause(); // 等待信号
    }

    return 0;
}

解析

  • signal(SIGINT, signalHandler):设置SIGINT信号的处理函数。
  • 子进程:等待2秒后,向父进程发送SIGINT信号。
  • 父进程:等待接收信号,当接收到SIGINT信号时,调用信号处理函数signalHandler

运行结果

父进程等待信号...
收到信号: 2

5、套接字(Sockets)

概念与原理

套接字是一种通用的通信机制,支持本地和网络通信。套接字提供了双向、可靠的字节流传输,常用于网络编程。本地套接字(Unix域套接字)可以用于同一主机上进程间的通信。

核心点
  • 创建套接字:使用socket系统调用创建套接字。
  • 绑定地址:使用bind系统调用绑定地址。
  • 监听和连接:使用listenaccept系统调用监听和接受连接。
  • 发送和接收数据:使用sendrecv系统调用发送和接收数据。
实现实例

服务端代码(server.cpp)

#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define SOCKET_PATH "/tmp/unix_socket"

int main() {
    int server_fd, client_fd;
    struct sockaddr_un server_addr;

    server_fd = socket(AF_UNIX, SOCK_STREAM, 0); // 创建套接字
    if (server_fd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, SOCKET_PATH);
    unlink(SOCKET_PATH);

    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { // 绑定套接字
        perror("bind");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 5) < 0) { // 监听连接
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    std::cout << "等待连接...\n";
    client_fd = accept(server_fd, NULL, NULL); // 接受连接
    if (client_fd < 0) {
        perror("accept");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    char buffer[100];
    read(client_fd, buffer, sizeof(buffer)); // 读取数据
    std::cout << "收到消息: " << buffer << std::endl;

    const char* response = "消息已收到";
    write(client_fd, response, strlen(response)); // 发送回复

    close(client_fd);
    close(server_fd);
    unlink(SOCKET_PATH); // 删除套接字文件

    return 0;
}

客户端代码(client.cpp)

#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define SOCKET_PATH "/tmp/unix_socket"

int main() {
    int client_fd;
    struct sockaddr_un server_addr;

    client_fd = socket(AF_UNIX, SOCK_STREAM, 0); // 创建套接字
    if (client_fd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, SOCKET_PATH);

    if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { // 连接服务端
        perror("connect");
        close(client_fd);
        exit(EXIT_FAILURE);
    }

    const char* msg = "来自客户端的消息";
    write(client_fd, msg, strlen(msg)); // 发送消息

    char buffer[100];
    read(client_fd, buffer, sizeof(buffer)); // 读取回复
    std::cout << "收到回复: " << buffer << std::endl;

    close(client_fd);

    return 0;
}

解析

  • 服务端

    • 创建套接字并绑定到文件路径。
    • 监听连接,并接受客户端的连接请求。
    • 从连接中读取数据并打印,然后发送回复。
    • 关闭连接并删除套接字文件。
  • 客户端

    • 创建套接字并连接到服务端。
    • 发送消息给服务端并读取回复。
    • 关闭连接。

运行结果

// Server output
等待连接...
收到消息: 来自客户端的消息

// Client output
收到回复: 消息已收到

四、总结

通过上述详细的概念、原理和实例,我们深入理解了C++进程间通信的多种技术。这些技术各有优缺点和适用场景:

  • 管道和命名管道:适用于父子进程间的简单数据传输。
  • 消息队列:适用于消息传递和排队处理,具有较好的灵活性。
  • 共享内存:适用于需要高效传输大数据量的场景,速度最快。
  • 信号:适用于异步事件通知和进程控制。
  • 套接字:适用于网络通信和本地进程间的通用通信。

        在实际应用中,选择合适的IPC机制对于系统性能和稳定性至关重要。希望本文对你在C++进程间通信的学习和应用中有所帮助。

上一篇:学懂C++(三十五):深入详解C++ 多线程编程性能优化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿享天开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值