目录
进程间通信介绍
进程间通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信发展
- 管道
- System V进程间通信
- POSIX进程间通信
进程间通信分类
管道
- 匿名管道pipe
- 命名管道
System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
管道
什么是管道
- 管道是Unix中最古老的进程间通信的形式。
- 我们把从一个进程连接到另一个进程的一个数据流称为一个"管道"
匿名管道
cpp#include <unistd.h> 功能:创建一无名管道 原型 int pipe(int fd[2]); 参数 fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端 返回值:成功返回0,失败返回错误代码
实例代码
cpp#include <iostream> #include <unistd.h> #include <cerrno> #include <cstring> #include <sys/wait.h> #include <sys/types.h> #include <string> const int size = 1024; std::string getOtherMessage() { static int cnt = 0; std::string messageid = std::to_string(cnt); cnt++; pid_t self_id = getpid(); std::string stringpid = std::to_string(self_id); std::string message = "message: "; message += messageid; message += "my pid is : "; message += stringpid; return message; } //子进程写入 void Write(int wfd) { std::string message = "father,I am son process!"; while(true) { std::string info = message + getOtherMessage();//子进程发给父进程的消息 write(wfd,info.c_str(),info.size()); sleep(1); } } //父进程读取 void Read(int rfd) { char inbuffer[size]; while(true) { ssize_t n = read(rfd,inbuffer,sizeof(inbuffer) - 1); if(n > 0) { inbuffer[n] = 0; std::cout<< "father get message: " <<inbuffer <<std::endl; } } } int main() { //1.创建管道 int pipefd[2]; int n = pipe(pipefd); if(n != 0) { std::cerr << "cerrno: " << errno << ": "<<"errstring: "<<strerror(errno) <<std::endl; return 1; } std::cout << "pipefd[0]: " <<pipefd[0] <<", pipefd[1]: "<<pipefd[1]<<std::endl; //2.创建子进程 pid_t id = fork(); if(id == 0) { std::cout<<"子进程关闭不需要的fd了,准备发消息了"<<std::endl; sleep(1); //子进程 -- write close(pipefd[0]); Write(pipefd[1]); exit(0); } std::cout<<"父进程关闭不需要的fd了,准备收消息了"<<std::endl; sleep(1); //父进程 -- read close(pipefd[1]); Read(pipefd[0]); pid_t rid = waitpid(id,nullptr,0); if(rid>0) { std::cout << "wait child process done"<<std::endl; } return 0; }
我们简单的写了一个父子进程的代码,我们的子进程来写,父进程来读,所以我们关闭了父进程的pipefd[1]写端,关闭了子进程的pipefd[0]读端。
用fork来共享管道原理
这个原理就相当于我们父子进程共享pipefd,各自保留自己需要的fd达到通信的过程。
管道读写规则
当没有数据可读时
- O_NONBLOCK disable(禁用):read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
- O_NONBLOCK enable(启用):read调用返回-1,errno值为EAGAIN。
当管道满的时候
- O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
- O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
补充:
O_NONBLOCK
的基本含义
启用 (
O_NONBLOCK
) : 当一个文件描述符(如套接字)以O_NONBLOCK
标志打开时,对该文件描述符执行的 I/O 操作(如读取、写入)将不会阻塞调用线程。如果操作不能立即完成(例如,因为网络数据尚未到达),这些操作将立即返回一个错误(通常是EAGAIN
或EWOULDBLOCK
),而不是让线程等待操作完成。禁用 (
~O_NONBLOCK
或无O_NONBLOCK
) : 默认情况下,如果不使用O_NONBLOCK
标志打开文件描述符,I/O 操作将阻塞调用线程,直到操作完成。例如,如果尝试从套接字读取数据而当前没有数据可读,调用线程将被挂起,直到数据到达。如果所有管道写端对应的文件描述符被关闭,则read返回0。
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出。
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
管道特点
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
- 管道提供流式服务
- 一般而言,进程退出,管道释放,所以管道的生命周期随进程
- 一般而言,内核会对管道操作进行同步与互斥
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
命名管道
- 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
- 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 命名管道是一种特殊类型的文件
创建一个命名管道
- 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
cppmkfifo filename
- 命名管道也可以从程序里创建,相关函数有:
cppint mkfifo(const char *filename,mode_t mode);
匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。
命名管道的打开规则
如果当前打开操作是为读而打开FIFO时
- O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
- O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
- O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
- O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
进程间通信及管道(理论)
板鸭〈小号〉2025-05-31 14:47
相关推荐
小虾米vivian20 分钟前
达梦数据库:同1台服务器如何启动不同版本的DMAP服务求真得真1 小时前
Predixy的docker化珊珊而川2 小时前
docker不用dockerfileTA远方3 小时前
【C#】一个简单的http服务器项目开发过程详解一个不知名程序员www3 小时前
Linux基本指令/下zhcong_3 小时前
LVS+Keepalived高可用群集Angel Q.4 小时前
系统是win11+两个ubuntu,ubuntu20.04和ubuntu22.04,想删除ubuntu20.04且不用保留数据JzjSunshine4 小时前
配置远程无密登陆ubuntu服务器时无法连接问题排查爱奥尼欧4 小时前
【Linux】环境变量完全解析Simon—欧阳4 小时前
微信小程序真机调试时如何实现与本地开发环境服务器交互