目录
一、进程间通信(IPC):
二、管道 来源于生活
三、匿名管道:在本地机器上可以实现父进程与子进程之间的通信
匿名管道的使用:
操作接口:
管道特性:
管道符代码实现:
命名管道(Named Pipes)
接口:
命名管道独有的特性:
演示:
写入端代码:
读入端代码:
是什么? 操作系统为用户提供的几种进程间通信方式,让进程之间能够进行通信,
为什么? 进程之间无法直接通信,而需要操作系统提供通信方式(进程之间具有独立性)
一个进程在访问一个数据的时候都是通过地址来进行访问的,然而进程中的地
址都是虚拟地址,经过页表映射之后访问屋里内存。
因此如果想要给另一个进程传递一个数据,就得把空间地址传递给其他进程
怎么做? 操作系统通过提供进程间通信方式,就是给多个需要通信的进程之间建立一个关联
:能够共同访问的一块内存
进程间不同的几种通信方式:管道、共享内存、消息队列、信号量。
作用:用于实现资源(数据)传输
特性:半双工通信——可以选择方向的单向通信(同一时间不能同时即发送又访问)
本质:内核中的一块缓冲区(内核空间中的一块内存,多个进程通过访问同一个缓冲区来进行通信)
分类:
匿名管道:没有描述符,不能被其他进程找到,只能具有亲缘关系的进程间通信
命名管道:有描述符,可以被其他进程找到,因此可以用于同一主机上的任意进程间通信
管道自带互斥和同步。
互斥:通过管道的唯一访问来保证进程运行的安全性
管道的读写操作在不超过一个buf 4096个字节大小时,保证原子性
原子性:不可分割特性 原子操作:一个操纵不会被打断
同步:通过对资源进行条件限制,来保证进程访问的合理性
例如:管道没有数据,read会阻塞,管道数据满了,write会阻塞。
①不支持跨网络之间的俩个进程的通信(本地连接)
②仅实现亲缘关系(父子进程)间通信,而不是俩个任意的进程
③只提供单向通信,如果俩个进程访问这个文件,进程1给文件中写入数据,那么进程2只能读取文件。
④管道是基于字节流来进行通信的
匿名管道主要用于本地父进程和子进程之间的通信,在父进程中的话,首先是要创建一个匿名管道,在创建匿名管道成功后,可以获取到对这个匿名管道的读写句柄,然后父进程就可以向这个匿名管道中写入数据和读取数据了,但是如果要实现的是父子进程通信的话,那么还必须在父进程中创建一个子进程,同时,这个子进程必须能够继承和使用父进程的一些公开的句柄,为什么呢?因为在子进程中必须要使用父进程创建的匿名管道的读写句柄,通过这个匿名管道才能实现父子进程的通信,所以必须继承父进程的公开句柄。同时在创建子进程的时候,必须将子进程的标准输入句柄设置为父进程中创建匿名管道时得到的读管道句柄,将子进程的标准输出句柄设置为父进程中创建匿名管道时得到的写管道句柄。然后在子进程就可以读写匿名管道了。
int pipe(int pipefd[2]);
功能:创建一个管道,并通过参数返回管道的俩个操作句柄
参数:pipefd——具有俩个整形元素的数组,内部创建管道会将描述符存储在数组中
pipefd[0]——用于从管道中读取数据
pipefd[1]——用于向管道中写入数据
返回值:成功返回0,失败返回-1
创建匿名管道,一定要在创建子进程之前。
1、管道中如果没有数据,则read从管道中读取数据就会被阻塞,直到有数据了,读取到数据之后才返回。
编译结果发现读端也进入打印,等待三秒写端进行读入之后读端继续读数据,说明读端一直在等待管道中的数据被写入。
2、如果管道中数据满了,则write继续向管道中写入数据会阻塞,直到管道中有剩余空间才行(有数据被取出去)
3、管道的所有读端被关闭,则继续向管道中写入数据会导致进程崩溃退出
13 if(ret < 0)
14 {
15 perror("pipe error");
16 return -1;
17 }
18
19 int pid1 = fork(); //创建子进程1之前管道已经存在,因此子进程1也有句柄
20 if(pid1 == 0)
21 {
22 // child1
23 sleep(3);
W> 24 char* data = "i am father\n";
25 printf("开始打印\n");
26 int ret = write(pipefd[1], data, strlen(data));
27 if(ret < 0)
28 {
29 perror("write error\n");
30 }
31 printf("write over\n");
32 exit(0);//为了能让子进程1退出,不继续向下
33 }
34
35 // 能走下来的只有父进程
36 int pid2 = fork(); // 创建子进程2之前管道已经存在,因此子进程2也有句柄
37 if(pid2 == 0)
38 {
39 close(pipefd[0]); // 关闭读端
40 exit(0);
41 while(1)
42 {
43 sleep(1);
44 printf("现在老二已经醒了,开始接受数据");
45 char buf[1024] = {0};
46 int ret = read(pipefd[0], buf, 1023);
if(ret == 0)
49 {
50 printf("所有写端已经被关闭!\n");
51 break;
52 }
53
54 printf("老二收到了老大的警告:%s\n",buf);
55 }
56 exit(0);//为了让子进程2退出,不继续向下
57
58 }
59
60 close(pipefd[0]); // 关闭所有的读端
61 wait(NULL);
62 wait(NULL);
63 return 0;
64 }
4、管道的所有写端被关闭,则read从管道中读取完所有数据后,将不在阻塞,而是返回0;
可以通过read的返回值,来决定什么时候停止从管道读取数据
#include
2 #include
3 #include
4 #include
5
6 int main()
7 {
8 int pipefd[2] = {0};
9
10 int ret = pipe(pipefd);
11 if(ret < 0)
12 {
13 perror("pipe error");
14 return -1;
15 }
16
17 pid_t ps_pid = fork();
18 if(ps_pid == 0)
19 {
20 close(pipefd[0]);
21 dup2(pipefd[1], 1); // 将标准输出重定向到管道写入端,操作标准输出就等于操作管道写入端
22 execlp("ps", "ps", "-ef", NULL);
23 exit(-1);
24 }
25 pid_t grep_pid = fork();
26 if(grep_pid == 0)
27 {
28 close(pipefd[1]);
29 dup2(pipefd[0], 0); // 将标准输入重定向到管道读取端,操作标准输入就等于操作管道读取段
30 execlp("grep", "grep", "pipe", NULL);
31 exit(-1);
32 }
33 close(pipefd[0]);
34 close(pipefd[1]);
35 waitpid(ps_pid, NULL, 0);
36 waitpid(grep_pid, NULL, 0);
37 return 0;
38 //wait(NULL);
39 //wait(NULL);
40 }
命名管道可在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。
服务器建立命名管道时给它指定一个名字(标识符),任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。
int mkfifo(char *pathname, mode_t mode)
pathname: 管道文件名称
mode: 管道文件的访问权限
返回值:成功返回0,失败返回-1;
若以只写的方式打开管道文件,则会阻塞,直到管道被任意进程以读的方式打开
若以只读的方式打开管道文件,则会阻塞,直到管道被任意进程以只写的方式打开。
因为一个管道如果不构成同时读写具备,就没有必要开辟缓冲区
新建一个会话端口打开写入端进行写入,那么之前的读入端口就会进行读取数据
左图为写入端,右下角图为读入端口。
接着我关闭写端: 管道特性:所以写端被关闭 read返回一个0,打印写端被关闭
那么我们之前看到的所以匿名管道具有的特点命名管道也具有。
#include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8
9 int main()
10 {
11 umask(0);// 将当前进程的文件权限掩码设置为0
12 int ret = mkfifo("./test.fifo",0664);
13 if(ret < 0 && errno != EEXIST) // errno是一个全局变量
14 {
15 perror("mkfifo error");
16 return -1;
17 }
18
19 int fd = open("./test.fifo", O_WRONLY); // 管道文件这里打开
20 if(fd < 0)
21 {
22 perror("open error");
23 return -1;
24 }
25
26 while(1)
27 {
28 printf("小明:");
29 fflush(stdout);
30 char buf[1024] = {0};
31 scanf("%s",buf);
32
33 int ret = write(fd, buf, strlen(buf));
34 if(ret < 0)
35 {
36 perror("write error");
37 close(fd);
38 return -1;
39 }
40 }
41 close(fd);
42 return 0;
}
#include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8
9 int main()
10 {
11 umask(0);// 将当前进程的文件权限掩码设置为0
12 int ret = mkfifo("./test.fifo",0664);
13 if(ret < 0 && errno != EEXIST) // errno是一个全局变量
14 {
15 perror("mkfifo error");
16 return -1;
17 }
18
19 int fd = open("./test.fifo", O_RDONLY); // 管道文件这里打开
20 if(fd<0)
21 {
22 perror("open error");
23 return -1;
24 }
25 while(1)
26 {
27 char buf[1024] = {0};
28 int ret = read(fd, buf, 1023);
29 if(ret < 0)
30 {
31 perror("write error");
32 close(fd);
33 return -1;
34 }else if(ret == 0)
35 {
36 printf("所有写端被关闭!\n");
37 close(fd);
38 return -1;
39 }
40 printf("小明:%s\n", buf);
41
42 }
close(fd);
return 0;
}