为什么要通信
我们需要不同进程之间相互协同,进程之间的协同本质上是程序员之间的协同,有可能为要从数据库中拿数据,你要从数据库中将数据格式化,写成特定的格式,我要根据特定的格式做数据统计,这一件事情,有一个无法实现这个事情就无法完成,
- 我们可以将这一件事情拆分成3个事情:一个进程专门从数据库中拿数据,一个进程专门做数据格式化,还有一个进程专门做数据统计
- 第一个处理完之后交给第二个,第二个处理完之后交给第三个
- 这样我们就可以实现业务上的通信,这样就增加了代码的可维护性,这样我们就找专门的进程处理特定的问题
如:
管道
通信的宏观理解
进程之间可能会存在特定的协同工作的场景------> 一个进程要把自己的数据交付给了一个进程,让其处理---------->这就叫做进程间通信------> 但是进程具有独立性,交互数据,成本一定很高,
(所以这一定需要操作系统进行协同,参与通信)
因为一个进程是看不到另一个进程的资源
所以两个进程要相互通信------->必须要先看到一份公共的资源(一方往里面放数据,另一方往里面取数据 )(这里的资源,就是一段内存)----> (这段公共进程属于谁,只能属于操作系统,)!!,因为进程具有独立性
这段内存:可能以文件的方式存在,也可能以队列的方式存在,也可能提供的就是原始的内存块
(这也是通信方式有很多的原因)
进程间通信本质上:其实是有OS参与,提供一封所有通信进程都能看到的公共资源,
结论
- 我们要想办法让我们看到一份公共的资源,
- 通信的时候操作系统的方式不同,所以通信的方式也就不同
进程间通信分类
管道
- 匿名管道
- 命名管道
System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
管道
匿名管道
父子进程是独立的进程,所以父子通信也属于进程通信的一种
让双方进程看到了同一份进程
接下来我们还想要让他钔通信
我们可以使用系统调用
PIPE(2) Linux Programmer's Manual PIPE(2)
NAME
pipe, pipe2 - create pipe
SYNOPSIS
#include <unistd.h>
int pipe(int pipefd[2]);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int pipe2(int pipefd[2], int flags);
DESCRIPTION
pipe() creates a pipe, a unidirectional data channel that can be used for interprocess communication. The array pipefd is used to return two file descriptors referring to the ends of the
pipe. pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the write end of the pipe. Data written to the write end of the pipe is buffered by the kernel until it is read
from the read end of the pipe. For further details, see pipe(7).
If flags is 0, then pipe2() is the same as pipe(). The following values can be bitwise ORed in flags to obtain different behavior:
O_NONBLOCK Set the O_NONBLOCK file status flag on the two new open file descriptions. Using this flag saves extra calls to fcntl(2) to achieve the same result.
O_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the two new file descriptors. See the description of the same flag in open(2) for reasons why this may be useful.
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
ERRORS
EFAULT pipefd is not valid.
EINVAL (pipe2()) Invalid value in flags.
EMFILE Too many file descriptors are in use by the process.
ENFILE The system limit on the total number of open files has been reached.
- pipefd[2]是一个输出型参数;意味着我们向通过这个参数读取到打开的两个fd,就是把这个值带出去,
- pipe(pipefd[2]),数组名就是首元素地址,到时候通过指针把文件描述符拷贝到我们的数组之中,
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
int fd[2];
if(pipe(fd)!=0)
{
perror("pipe");
exit(-1);
}
printf("pipe[0]=%d\n",fd[0]);
printf("pipe[1]=%d\n",fd[1]);
return 0;
}
我们发现一个是3,一个是4
匿名管道的特点
- 管道是一个单向的流通信号,
- 管道是面向字节流的,tcp FILE,fstream,我们读的时候是只有字节的概念,并不是说我想读多少,就读多少
- 仅限于父子特性
- 管道回自带同步机制,管道里面没有数据就不会读到管道里面的废弃数据,原子性写入
- 管道的生命周期是随进程的
我们测试了一下,写段一直不写,读端一直不读
写满64KB 的时候,write就不再写入了,为什么呢?因为管道有大小!当write写满的时候,要让reader来读,同样的读端也要等写端,我们不会出现读取新老数据的覆盖事件,
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include <string.h>
int main()
int main()
{
int fd[2];
if(pipe(fd)!=0)//创建管道
{
perror("pipe");
exit(-1);
}
//打印文件描述符
printf("pipe[0]=%d\n",fd[0]);
printf("pipe[1]=%d\n",fd[1]);
//实现进程键通信
pid_t id =fork();
if(id<0)
{
perror("fork");
exit(1);
}
//我们向让父进程执行读取,子进程执行写入
//0----->就是读取端,1----->就是写入端
else if(id==0)//创建子进程
{
//child
//关闭读写端,建立一个单向的管道
//子进程把文件描述符也都给继承下来了
close(fd[0]);
const char* msg="hello world";
int cout=0;
//我们还可以使用read 和write进行读写操作
while(1)
{
//write(fd[1],msg,strlen(msg));//我们不需要+1,因为\0本身只是c语言的标准,
write(fd[1],"a",1);//子进程不断往管道里面写入,但是父进程不读
cout++;
printf("cout=%d\n",cout);
//write的动作就是pipe里面只要有缓冲区区域就一直写入
//当我们一直些的时候,写满64KB的时候,写段就不再写入了,因为管道有大小,
//当写端写满的时候,为什么不写了,
//因为要让读端来读,
}
exit(0);
//dup2(fd[1],1);
}
else
{
//father
close(fd[1]);
//父进程就一直在那里读取
//while(1)
//{
// char buf[32]={0};
// ssize_t ret=read(fd[0],buf,sizeof(buf));//如果此时ret=0说明子进程关闭文件描述符了,相当于读到文件的结尾了
// //read是只要有数据就可以一直读取
// //这就叫字节流
// if(ret==0)
// break;
// else
// {
// buf[ret]=0;//把\0位置就设置为0
// printf("child say : %s\n",buf);
// //我们没有让父进程去sleep
// }
dup2(fd[0],0);//绑定打印到文件里面
//}
sleep(1);
//dup2(fd[0],0);
}
return 0;
}
写段被关闭了,读端继续读,就会都到文件的最后
当我们的读端关闭,写端还在写入,此时站在操作系统的角度,合理吗??
严重不合理,因为已经没有人读了,你还在写入,本质就是在浪费操作系统的资源,所以OS会直接终止写入进程,OS给目标进程发送信号,SIGPIPE
管道的四种情况:
- 读端不读或者读的满,那么写端要等读端读完
- 读端关闭,没人读了,写端就没有了存在的必要,写端直接受到SIGPIPE 的信号,直接终止
- 写端不写或者写的慢,读端就要等写端
- 写端关闭,读端就读完全部pipe内部的数据,然后再读就会读到0,表面文件的结尾!
管道实在具有血缘关系的进程之间可以进行进程间通信,但是常用于父子间通信的
管道是文件吗?
如果一个文件只能被当前进程打开,相关进程退出了,被打开的文件呢?会被OS自动关闭,
可以看我的github仓库详细情况
匿名管道代码详细