文章目录
一、进程间通信介绍
1.进程间通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知他们发生了某种时间(如进程终止时要通知父进程)
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
2.进程间通信发展
- 管道
- System V 进程间通信
- Posix 进程间通信
3.进程间通信分类
管道:匿名管道pipe 命名管道
System V IPC :消息队列 共享内存 信号量
POSIX IPC:消息队列 共享内存 信号量 互斥量 条件变量 读写锁
二、管道
1.管道定义
- 管道是Unix中最古老的进程间通信的方式
- 将一个进程连接到另一个进程的一个数据流称为 -- 管道
- 通常用来具有血缘关系的进程,常用于父子通信
创建管道: int pipefd[2] = {0}; //数组降维
int n = int pipe (int pipefd[2]);
if(n <0) //创建出错
if(n == 0) //创建成功
其中pipefd[2]是一种输出型参数。创建管道时候要获得读端和写端,即需要读写的fd。创建管道成功,返回0,否则返回-1.
1.1匿名管道
#include<unistd.h> //创建一无名管道 int pipe(int fd[2]); // fd:文件描述符组,fd[0] 表示读 fd[1] 表示写 返回值: 成功返回0 ,失败返回错误代码
实例代码:从键盘读取数据写入管道,读取管道写到屏幕
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main( void )
{
int fds[2];
char buf[100];
int len;
if ( pipe(fds) == -1 )
perror("make pipe");
exit(1);
// read from stdin
while ( fgets(buf, 100, stdin) ) {
len = strlen(buf);
// write into pipe
if ( write(fds[1], buf, len) != len ) {
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
// read from pipe
if ( (len=read(fds[0], buf, 100)) == -1 )
{
perror("read from pipe");
break;
}
// write to stdout
if ( write(1, buf, len) != len )
{
perror("write to stdout");
break;
}
}
}
2.用fork来共享管道原理
fork成功,返回0,创建成功了子进程,返回-1,创建子进程失败
//任何一种通信,一定要首先保证不同的进程之间可以看到同一份资源
int main()
{
//父进程创建管道
int pipefd[2] = {0}; //pipefd[0] 读 pipefd[1] 写
int n = pipe(pipefd[2]);
pid_t id = fork();
int n = pipe(pipefd[0]);
//创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
//创建了子进程 看到了同一份资源 :管道
//子进程写入,关闭读端
close(pipefd[0]);
//开始通信 结合某种场景
const std::string namestr = "hello,我是子进程";
int cnt = 1;
char buffer[1024]; //组合身份,cnt发送给父进程
while(true)
{ //将后面的数据打包,给buffer
snprintf(buffer,sizeof(buffer), "%s,计数器:%d ,我的ID:%d\n",namestr.c_str(),cnt++,getpid());
//往管道文件里写入 将buffer指向的内存写入strlen(buffer)个,写到pipefd[1]所指的文 件内
write(pipefd[1],buffer,strlen(buffer));
sleep(1);
}
//通信结束,关闭写入端
close(pipefd[1]);
exit(0);
}
//父进程
char buffer[1024];
while(true)
{
int n = read(pipefd[0],buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n] = '\0';
std::cout<< "我是父进程,child give me message: " <<buffer<<std::endl;
}
}
//关闭不需要的fd 父进程读 子进程写
close(pipefd[1]);
}
3.站在文件描述符角度深度理解管道
首先,父进程创建出管道,占用两个文件描述符读和写,父进程fork出子进程,继承了父进程,此时有两组fd看到了同一份管道资源。然后,父进程关闭读/写文件描述符,子进程也关闭读/写文件描述符,然后开始在各自的进程中执行读/写操作,开始通信。
4.站在内核角度-管道本质
管道的本质和文件一样,使用和文件一致。
5.管道读写规则
- 当没有数据可读或管道满了 ,进程处于阻塞状态
- 当要写入的数据量小于PIPE_BUF时,linux将保证写入的原子性
- 当写入的数据量小于PIPE_BUF时,linux将不能保证写入的原子性
6.管道特点
- 单向通信
- 管道的本质是文件,因为fd的生命周期随进程,管道的生命周期是随进程的
- 父进程和子进程可以通过管道通信的本质:父进程和子进程有继承关系,可以看到同一份文件(管道通信通常用来具有血缘关系的进程,进行进程间通信,常用于父子通信)
- pipe打开管道,并不清楚管道的名字,称为匿名管道
- 在管道通信中,写入的次数和读取的次数不是严格匹配的(比如写100次,读50次)读写没有强相关 --面向字节流
- 子进程write写慢点,发现父进程读取也变慢了-------------------
- ①如果read读取完毕所有的管道数据,对方不发送,只能等待
- ②子进程write写入变快,父进程每隔10s再进行读取--管道文件是有大小的 65535(2^16),写端将管道写满了,就不能再写了
- ③管道具有一定的协同能力,读写按照一定的步骤进行通信--自带同步机制
- ④ 关闭了写端,读端一直读--当读取完毕管道数据,再读read到0,表示父进程读到了文件结尾
- ⑤写端一直写,读端关闭,没有意义,os不会维护无意义低效率的进程,此时os会kill一直在写入的进程(通过发送13号信号SIGPIPE 终止进程)
- 管道是半双工的,数据只能一个方向流动,需要双方通信时,需要建立起两个管道