
受排版的限制,上面的思维导图显示的不是很完整。下面是思维导图源文件的获取方式:
思维导图获取
提取码: diid
问题:进程间通信是什么?
进程间通信就是 进程和进程 之间进行通信。
问题:进程间通信的目的?(为什么要有进程间通信)
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源
通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件(如进程终止时会去通知父进程)
进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦住另一个进程的所有陷入和异常,并能够及时知道它的状态改变
问题:进程间通信的分类?
管道:匿名管道pipe、命名管道
System V IPC:System V 消息队列、System V 共享内存、System V 信号量
POSIX IPC:消息队列、共享内存、信号量、互斥量、条件变量、读写锁
问题:进程间通信的本质是什么?
进程间通信的本质就是让不同的进程,看到同一份资源。
问题:怎么做到进程间通信?
ps: 由于进程运行时具有独立性,因此进程间通信时,一般要借助第三方(OS)资源 #OS要提供一段内存区域,该区域能被双方进程看到
通信的本质就是“数据的拷贝”
匿名管道pipe
匿名管道的使用
int pipe(int fd[2]);
参数:
fd:文件描述符数组,其中fd[0]代表read读端;fd[1]代表write写端;
返回值:
成功返回0;失败返回错误代码
演示
//父进程通过管道给子进程发消息
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <cstring>
using namespace std;
int main()
{
int fd[2];
pipe(fd); //创建匿名管道
pid_t id = fork();
if(id == 0)
{
//子进程
close(fd[1]); //关闭写端
char buffer[128];
ssize_t num = read(fd[0], buffer, sizeof(buffer));
buffer[num] = '\0'; //从文件中读取的字符串并不是以'\0'为结尾的
cout << buffer;
}
else
{
//父进程
close(fd[0]); //关闭读端
const char* buffer = "this is father porcess!\n";
write(fd[1], buffer, strlen(buffer));
wait(NULL);
}
return 0;
}
匿名管道的原理
父进程先创建一个匿名管道,然后再创建子进程。在子进程创建的时候,子进程会拷贝父进程的task_struct和files_struct结构体,而他们的files_struct结构体中的文件描述符指向了同一个文件,这样子进程就能看到父进程所创建的匿名管道了。
Tip:
管道虽然使用的是文件的方案,其实OS并不会把数据刷新到磁盘当中 (匿名和命名都是),因为刷新到磁盘的话,会降低效率(有IO参与,效率肯定会下降)。//刷新到磁盘也没有意义,2个进程只是借助文件实现通信,所以并不需要刷新到磁盘当中
父子进程通过匿名管道通信的过程
首先,父进程先使用pipe函数创建一个管道,一个进程在创建管道的时候,默认是同时打开读端fd[0]和写端fd[1]的。
父进程调用fork函数,子进程在被创建时会拷贝父进程的task_struct和files_struct结构体,而在files_struct中有2个文件描述符已经指向了一个匿名管道,所以子进程也能看到这个管道。
为了实现父子间通过管道进行通信,必须让其中1个关闭读端,另一个关闭写端。
管道的特点
- 管道只能进行单向通信
- 管道是半双工通信 (数据在同一时刻只能向1个方向流动,想要进行双向通信需要建立2个管道)
- 管道的内部提供了互斥与同步的机制
- 管道的生命周期跟随进程,一般情况下,进程退出,管道就会被释放
- 匿名管道适合具有血缘关系的进程进行通信 (父子进程、兄弟进程)
- 管道提供流式服务
这里大概解释一下管道特点中的个别几点:
问题:什么是半双工通信?什么是全双工通信?
全双工:在同一时刻,发送数据 和 接收数据 能同时进行 #同一时刻能双向
半双工:在同一时刻,只能进行发送 或 接收 #同一时刻只能单向
问题:什么是同步与互斥机制?
这个概念我将会放在多线程博客当中去讲解,这里就大概的介绍一下。互斥就是同一时刻只能有一个进程/线程正在使用临界资源,同步就是在确保了临界资源安全的前提下,以特定的顺序让进程/线程去访问临界资源。
管道的读写规则
- 正常进行写和读时,当write写完,关闭该进程的写端fd[1]。此时,读端read函数的返回值为0
- 写端还没写完,读端就关闭了,此时写端进程会被OS杀掉 (收到SIGPIPE信号)
- 不write,一直read,由于写端接收不到数据,就会出于阻塞状态
- 不read,一直write,当管道满了之后,写端就会进入阻塞状态
总结:
读管道:
1. 管道中有数据,read返回实际读到的字节数
2. 管道中无数据:
(1) 管道写端全部被关闭,read返回0
(2) 写端没有全部被关闭,在read处阻塞等待
写管道:
1. 管道读端全部被关闭,进程异常终止 (OS发送SIGPIPE信号给写端)
2. 读端没有全部被关闭:
(1) 管道未满时,write继续写入数据
(2) 管道写满了,write阻塞
管道的大小
命名管道
由于匿名管道只能作用于具有共同祖先的进程间通信,然而大部分的进程实际上都是不相关的,所以我们引入了命名管道,命名管道可以让完全不相干的2个进程进行通信。命名管道虽然是打开一个目录下的文件,但是实际上也是在内存当中进行操作,并不会刷新到磁盘当中。
命名管道的使用
命令行中创建:
mkfifo filename
程序中创建:
int mkfifo(const char* filename, mode_t mode);
//包含于<sys/types.h> <sys/stat.h>
参数:
filename:创建出来的命名管道的名称
mode:命名管道权限的设置
演示
//实现client端给server端发消息
//server.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
using namespace std;
#define FILE_NAME "myfifo"
int main()
{
mkfifo(FILE_NAME, 0666);//mkfifo的第一参数是文件名,它会给你创建个以这个为名字的命名管道
int fd = open(FILE_NAME, O_RDONLY);
char buffer[128];
while(1)
{
ssize_t num = read(fd, buffer, sizeof(buffer));
buffer[s] = 0;
//下面的代码是server端接收client端的字符串的消息
if(num < 0)
{
cout << "read error!" << endl;
break;
}
else if(num == 0)
{
cout << "End of File!" << endl;
break;
}
else
{
cout << buffer << endl;
}
}
return 0;
}
//client.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
using namespace std;
#define FILE_NAME "myfifo"
int main()
{
int fd = open(FILE_NAME, O_WRONLY);
const char* msg = "hello server, this is client";
int cnt = 5;
while(cnt--)
{
write(fd, msg, strlen(msg));
sleep(1);
}
sleep(100);
return 0;
}
匿名管道 VS 命名管道
匿名管道与命名管道的唯一区别就在于 ----- 管道的打开方式不同
匿名管道由pipe函数创建,并在创建时打开。
命名管道由mkfifo函数创建,打开需要使用open
管道的应用
- 可以实现进程之间互相传递消息(字符串)
- 可以让一个进程帮助另一个进程执行指令 (另一个进程接收消息后,使用execl函数执行指令)
- 可以让另一个进程帮助进行计算
- 可以实现“文件的拷贝” //文件的上传、下载,实际上都是文件的拷贝