目录
什么是进程间通信
管道
匿名管道
什么是进程间通信
进程间通信,顾名思义就是两个进程互相通信。
可是进程是独立的,该如何通信呢?
类比你和你的朋友在网上聊天,你们两个人也是独立的,是如何通信的呢?因为你能看到你朋友给你发的消息,你朋友也能看到这份消息。这个消息就是一个公共的资源。
所以进程间如果要进行通信,就需要两个进程看到同一份资源。
那么,通信的地点在哪里呢?
很明显,因为进程之间是独立的,所以通信的地点肯定不能在进程中,只能在操作系统中。
结论:进程间通信的地点是操作系统。
进程间通信的方式有很多,一般有三种:
1.管道:通过文件系统通信。
- 匿名管道
- 命名管道
2. system V:聚焦在本地通信
- 共享内存
- 消息队列
- 信号量
3. POSIX:让通信可以跨主机。
- 共享内存
- 消息队列
- 信号量
- 互斥量
- 条件变量
- 读写锁
本文主要讲管道的通信方式。
管道
回顾文件系统
当父进程打开一个文件时,创建了一个子进程。子进程会继承父进程,包括继承父进程的文件描述符。子进程和父进程相同的fd指向同一个文件。
到这里,通信的条件就具有了,因为父子进程看到了同一份文件。
说明:在冯诺依曼体系结构中,磁盘是外设。而外设的读取速度是比较慢的。但是在进程间通信时,他们并不关心磁盘中有什么文件 - - - 说白了就是不会和磁盘进行交互。于是为了提升效率,就直接关闭了和磁盘中文件的交互。所以,这种不进行IO通信的文件,叫做内存级文件。
那么,如果是内存级文件,需要如何进行通信呢?
1.父进程将内容写入到内存(struct file的内存缓冲区)中,
2.子进程从内存(struct file的内存缓冲区)中读取数据。
总共发生两次拷贝
结论:通过文件系统提供公共资源的进程间通信,叫做管道。
匿名管道
理论
定义:匿名管道,就是没有名字的管道。
使用范围:只能子在父子之间,或者父亲的所有孩子(兄弟)之间进行通信,也就是具有血缘关系才能通信。
原理:
第一步:父进程打开管道
从图中可以看到,fd[0]对应的是读端,对应父进程的3号文件描述符,
fd[1]对应的是写端,对应父进程的4号文件描述符。
fd[0]就是读端 fd[1]就是写端
第二步:父进程创建子进程,子进程会继承父进程的文件描述符
到这里,父子进程通信的管道(途径)就建立好了,但是管道就像水管一样,只能单向通信。所以,我们如果想要父进程写入,子进程读取,就需要关闭父进程的读端,子进程的写端。
第三步:父进程关闭读端(写端),子进程关闭写端(读端)
实践
查看手册,查找pipe函数。
解读:pipe函数的形参是一个pipefd[2]的数组,该数组有两个元素,分别是fd[0]和fd[1];其中,pipefd[0]代表读端,pipefd[1]代表写端。
返回值:如果创建成功,返回0,否则返回-1。
实现通过管道使父子进程通信
打印结果:
匿名管道读写特征(四种)
1.读快,写慢
写入慢,读取快,read调用阻塞,即进程暂停执行,一直等到有数据来到为止
2.读慢,写快
写入快,读取慢,write调用阻塞,直到有进程读走数据。
3.写入端关闭,读取端未关闭
管道写端对应的文件描述符被关闭,则read返回0
当写入端关闭了,读取端就没有等待的必要了,读取端的read()直接返回0,表示写入端关闭了。
4.读取端关闭,写入端未关闭
管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,让write进程退出。
匿名管道实现简易进程池
processPool.cc文件
task.hpp文件
运行结果:
命名管道
理论
使用范围:命名管道可以实现任何进程之间的通信。
命名管道实现的过程:
第一步:创建命名管道
使用指令:mkfifo 文件名 创建命名管道
创建命名管道之后,该如何让不同的进程看到同一份资源呢?
首先,我们需要将这个命名管道做唯一标识。
其次,让不同的文件都通过这个唯一标识,就能确定双方找到的文件是同一份文件了。
就好像钥匙和锁一样。现在有很多钥匙,A进程手里10把要是,B进程手里10把钥匙,现在A和B进程都找自己的钥匙,只要能打开同一把锁,就是相同的钥匙。
如何做到唯一标识?
之前在文件系统中说到,为什么同一个目录下的文件不能重名?就是因为目录下存的是文件的文件名,目录通过文件名找文件,所以,不能重名。
这里同理,利用同一路径 + 文件名的方式唯一标识文件。
第二步:进程之间使用命名管道通信
通信过程和匿名管道一样。
第三步:删除命名管道(如果不在程序中删除,就要自己手动删除)
使用指令:unlink 文件名
实践
创建命名管道
参数:
第一个参数是pathname,表示要创建的管道文件所在的路径和文件名
第二个参数是权限,一般设置0666或者0664
作用:
创建一个命名管道
返回值:
如果创建成功,则返回0,否则返回-1
使用:
进程之间使用命名管道通信
实现命名管道通信,需要两个进程,我们姑且将两个进程命名为客户端(client)和服务端(sliver),客户端发信息,服务端接受信息。
客户端写入
服务端读取
服务端程序
只读端,打开管道文件的方式是O_RDONLY。
接下来就是文件描述符fd中读取sizeof(buffer)个字节内容到buffer中。为了方便观察,将buffer中的内容打印到显示器上。
客户端程序
写入端打开文件的时候使用O_WRONLY
之后将内容输入到line中,将line中的内容写入到fd中,拱读取端去读