进程间通信
进程设计的特点之一就是独立性,要避免其他东西影响自身的数据
但有时候我们需要共享数据或者传递信息,传统的父子进程也只能父进程传递给子进程信息
因此进程间通信还是很必要的,除此之外网络通信中所说的进程间通信本质上也是两个进程进行通信,只不过信息传递经过了繁杂的计算机网络
进程间通信有很多类型,但是利用管道进行进程间通信对我们了解底层原理还是很有帮助的,因此我们也从管道开始介绍
管道
在我们学习Linux指令的时候,有一个字符是|
,用于将前一个指令的输出传递给下一个指令作为输入
通过这么久的学习,我们可以大概猜测出这个管道的原理是什么样的
两个指令本质上就是两个进程,利用管道进行通信,将两个管道连接起来
如何控制这两个进程的输入输出呢,其实就用到了之前的重定向功能
头一个指令的输出进行重定向,后一个指令的输入重定向就可以实现这样的功能了
再深入一点,管道的本质其实就是一个被打开的文件,但同时能被两个进程访问,而且只能一个进程写,另一个进程读
匿名管道
匿名管道通常用于父子进程的通信
父进程使用pipe函数创建管道
这个函数的参数是一个描述符数组,下标0表示读,下标1表示写,成功返回0,失败返回错误码
这个pipe函数实际上做了什么操作呢,可以简单理解为,操作系统创建了一个文件,然后再给两个文件标识符,一个用来表示读,一个用来表示写
然后再用fork函数创建子进程,由于子进程是写时拷贝父进程的,因此对子进程来说,他的文件描述符表也有一个读接口和一个写接口是指向匿名管道的
但是管道通信是半双工的,我们不允许两个进程同时输入或者输出,只允许单向传递
因此,我们直接关掉某一方的读,另一方的写
这样就能变成一个单向通信了
但是如果有需要实现双向通信,我们也可以在父进程开两个管道,然后对应做相应的操作就可以了
匿名管道使用
#include<iostream>
#include<cstring>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{
// 创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
assert(n==0); // 确保管道创建成功
// 创建子进程
pid_t pid = fork();
assert(pid>=0);
if(pid==0) // 子进程
{
close(pipefd[0]); // 关闭子进程的读
int count = 0;
string str = "a massage";
char send_buffer[1024];
while(count<=5)
{
snprintf(send_buffer, sizeof(send_buffer), "%d[%d]:%s", getppid(), count++, str.c_str()); // 写入缓冲区
write(pipefd[1], send_buffer, sizeof(send_buffer)); // 写入管道文件
}
cout<<"子进程退出"<<endl;
exit(0);
}
// 父进程
close(pipefd[1]); // 关闭父进程的写
char read_buffer[1024];
while(1)
{
ssize_t s = read(pipefd[0], read_buffer, sizeof(read_buffer)-1); // 从管道读到缓冲区
if(s>0) // 读到了内容
{
read_buffer[s] = '\0'; // 文件操作不endl;会自带'\0' 需要手动添加
cout<<"子进程说的内容是:"<<read_buffer<<endl;
}
else // 读到了0,说明写的接口已经关了
{
break;
}
}
pid_t ret = waitpid(pid, NULL, 0);
assert(ret>0);
return 0;
}
管道通常用于父子进程的通信,并且使用的是字节流的服务
管道是基于文件的,当进程结束的时候,管道也就会销毁
管道是有大小限制的,一般为64KB
当写比读快时,写满了就会写阻塞
当读比写快时,读完了就会读阻塞
写的一端关闭了,读的一端会读到0,表示结束
读的一端关闭了,写的一端继续写就会直接终止进程(操作系统通过信号终止)