当前位置: 首页 > news >正文

学懂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 strlenint 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]); // 关闭写端,使子进程看到EOFwait(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++ 多线程编程性能优化

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Controller中接收数组参数 post请求中在body中传+post请求中通过表单形式传(x-www-form-urlencoded)
  • Python接口自动化测试框架介绍
  • 使用FFmpeg更改视频播放速度的方法
  • 设计资讯 | 这款受数学方程启发的平板桌:配集成黑胶唱片机和无线充电器
  • 一篇文章带你了解网络安全就业前景,零基础入门到精通,收藏这篇就够了
  • React+Vis.js(03):vis.js设置节点形状
  • 用Scratch编程打造你的策略游戏:《保卫萝卜》入门教程
  • Groovy DSL从入门到项目实战(一)
  • 有性价比高的开放式耳机推荐吗?盘点开放式蓝牙耳机排行榜前十名
  • 【百度】25届秋招内推码
  • vscode中使用官方推荐的编程字体Cascadia Code字体
  • 深入解析JavaScript的XMLHttpRequest对象:异步Web通信的基石
  • Alembic:python中数据库迁移的瑞士军刀
  • NC 寻找第K大
  • java进阶学习路线
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • Android Studio:GIT提交项目到远程仓库
  • C++入门教程(10):for 语句
  • Debian下无root权限使用Python访问Oracle
  • HashMap ConcurrentHashMap
  • Java Agent 学习笔记
  • JS实现简单的MVC模式开发小游戏
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • ucore操作系统实验笔记 - 重新理解中断
  • Vue2.0 实现互斥
  • vue中实现单选
  • 彻底搞懂浏览器Event-loop
  • 分类模型——Logistics Regression
  • 给github项目添加CI badge
  • 前端面试之CSS3新特性
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 如何在GitHub上创建个人博客
  • 使用Swoole加速Laravel(正式环境中)
  • 数据结构java版之冒泡排序及优化
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • RDS-Mysql 物理备份恢复到本地数据库上
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • ​Redis 实现计数器和限速器的
  • ​力扣解法汇总946-验证栈序列
  • # 数仓建模:如何构建主题宽表模型?
  • #laravel 通过手动安装依赖PHPExcel#
  • (1)(1.9) MSP (version 4.2)
  • (C++17) std算法之执行策略 execution
  • (Forward) Music Player: From UI Proposal to Code
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (NSDate) 时间 (time )比较
  • (vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (附源码)springboot青少年公共卫生教育平台 毕业设计 643214
  • (每日一问)基础知识:堆与栈的区别
  • (亲测有效)推荐2024最新的免费漫画软件app,无广告,聚合全网资源!
  • (三)docker:Dockerfile构建容器运行jar包
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇