管道
概念
管道限制
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;
- 通常一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道
读写规则
- 当没有数据可读时
- O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据为止
- O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN
- 当管道满的时候
- O_NONBLOCK disbale:write调用阻塞
- O_NONBLOCK enable:write调用返回-1,errno值为EAGAIN
- 如果所有管道写端对应的文件描述符被关闭,则read返回0
- 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE
- 当要写入的数据量不大于PIPE_BUF时,linux将保持写入的原子性
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性
匿名管道与命名管道
- 匿名管道由pipe函数创建并打开
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一旦这些工资完成之后,它们具有相同的语义
匿名管道 pipe
创建
使用pipe系统调用
获取两个“文件描述符”,分别对应管道的读端和写端。
fd[0]: 是管道的读端
fd[1]: 是管道的写端
如果对fd[0]进行写操作,对fd[1]进行读操作,可能导致不可预期的错误
使用
单进程使用管道进行通信
创建管道后,获得该管道的两个文件描述符,不需要普通文件操作中的open操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h>
int main(void) { int fd[2]; int ret; char buff1[1024]; char buff2[1024];
ret = pipe(fd); if (ret !=0) { printf("create pipe failed!\n"); exit(1); }
strcpy(buff1, "Hello!"); write(fd[1], buff1, strlen(buff1)); printf("send information:%s\n", buff1);
bzero(buff2, sizeof(buff2)); read(fd[0], buff2, sizeof(buff2)); printf("received information:%s\n", buff2);
return 0; }
|
多进程使用管道进行通信
创建管道之后,再创建子进程,此时一共有4个文件描述符。
4个端口,父子进程分别有一个读端口和一个写端口。
向任意一个写端口写数据,即可从任意一个读端口获取数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h>
int main(void) { int fd[2]; int ret; char buff1[1024]; char buff2[1024]; pid_t pd;
ret = pipe(fd); if (ret !=0) { printf("create pipe failed!\n"); exit(1); }
pd = fork(); if (pd == -1) { printf("fork error!\n"); exit(1); } else if (pd == 0) { bzero(buff2, sizeof(buff2)); read(fd[0], buff2, sizeof(buff2)); printf("child process(%d) received information:%s\n", getpid(), buff2); } else { strcpy(buff1, "Hello!"); write(fd[1], buff1, strlen(buff1)); printf("parent process(%d) send information:%s\n", getpid(), buff1); }
if (pd > 0) { wait(0); } return 0; }
|
execl启动新程序进行通信
子进程使用exec启动新程序运行后,新进程能够使用原来进程的管道(因为exec能共享原来的文件描述符)
但问题是新进程并不知道原来的文件描述符是多少!最简单的方法就是把进程中的管道文件描述符,用exec的参数传递给新进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h>
int main(void) { int fd[2]; int ret; char buff1[1024]; char buff2[1024]; pid_t pd;
ret = pipe(fd); if (ret !=0) { printf("create pipe failed!\n"); exit(1); }
pd = fork(); if (pd == -1) { printf("fork error!\n"); exit(1); } else if (pd == 0) { sprintf(buff2, "%d", fd[0]); execl("main32", "main32", buff2, 0); printf("child process execl error!\n"); exit(1); } else { strcpy(buff1, "Hello!"); write(fd[1], buff1, strlen(buff1)); printf("parent process(%d) send information:%s\n", getpid(), buff1); }
if (pd > 0) { wait(0); } return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h>
int main(int argc, char* argv[]) { int fd; char buff[1024] = {0,};
sscanf(argv[1], "%d", &fd); read(fd, buff, sizeof(buff));
printf("execl Process(%d) received information:%s\n", getpid(), buff); return 0; }
|
关闭管道的读写端
对管道进行read时,如果管道中已经没有数据了,此时读操作将被“阻塞”。
如果此时管道的写端已经被close了,则写操作将可能被一直阻塞!而此时的阻塞已经没有任何意义了。(因为管道的写端已经被关闭,即不会再写入数据了)
如果不准备再向管道写入数据,则把该管道的所有写端都关闭,此时再对该管道read时,就会返回0,而不再阻塞该读操作。(管道的特性)
如果有多个写端口,而只关闭了一个写端,那么无数据时读操作仍将被阻塞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h>
int main(void) { int fd[2]; int ret; char buff1[1024]; char buff2[1024]; pid_t pd;
ret = pipe(fd); if (ret !=0) { printf("create pipe failed!\n"); exit(1); }
pd = fork(); if (pd == -1) { printf("fork error!\n"); exit(1); } else if (pd == 0) { close(fd[1]); bzero(buff2, sizeof(buff2)); read(fd[0], buff2, sizeof(buff2)); printf("child process(%d) received information:%s\n", getpid(), buff2); } else { strcpy(buff1, "Hello!"); close (fd[0]); write(fd[1], buff1, strlen(buff1)); printf("parent process(%d) send information:%s\n", getpid(), buff1); close (fd[1]); }
if (pd > 0) { wait(0); } return 0; }
|
把管道作为标准输入和标准输出
把管道作为标准输入和标准输出的优点:
子进程使用execl启动新程序时,就不需要再把管道的文件描述符传递给新程序了。
可以直接使用使用标准输入(或标准输出)的程序。
比如 od –c
(统计字符个数,结果为八进制)
实现原理:
使用dup复制文件描述符
用exec启动新程序后,原进程中已打开的文件描述符仍保持打开,即可以共享原进程中的文件描述符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h>
int main(void) { int fd[2]; int ret; char buff1[1024]; char buff2[1024]; pid_t pd;
ret = pipe(fd); if (ret !=0) { printf("create pipe failed!\n"); exit(1); }
pd = fork(); if (pd == -1) { printf("fork error!\n"); exit(1); } else if (pd == 0) { close(fd[1]);
close(0); dup(fd[0]); close(fd[0]); execlp("./main52", "./main52", "-c", 0); printf("child process execl error!\n"); exit(1); } else { close(fd[0]); strcpy(buff1, "Hello!\n"); write(fd[1], buff1, strlen(buff1));
strcpy(buff1, "world!\n"); write(fd[1], buff1, strlen(buff1));
close(fd[1]); } return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <stdio.h> #include <stdlib.h>
int main(void) { int ret = 0; char buff[80] = {0,};
ret = scanf("%s", buff); printf("main52 recv %s\n", buff);
ret = scanf("%s", buff); printf("main52 recv %s\n", buff); return 0; }
|
popen
popen用来在两个程序之间传递数据
在程序A中使用popen调用程序B时,有两种用法:
程序A读取程序B的输出(使用fread读取)
程序A发送数据给程序B,以作为程序B的标准输入。(使用fwrite写入)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <stdio.h> #include <stdlib.h>
#define BUFF_SIZE 1024
int main(void) { FILE * file; char buff[BUFF_SIZE+1]; int cnt;
file = popen("ls -l", "r"); if (!file) { printf("fopen failed!\n"); exit(1); }
cnt = fread(buff, sizeof(char), BUFF_SIZE, file); if (cnt > 0) { buff[cnt] = '\0'; printf("%s", buff); }
pclose(file);
return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <stdio.h> #include <stdlib.h> #include <string.h>
#define BUFF_SIZE 1024
int main(void) { FILE * file; char buff[BUFF_SIZE+1]; int cnt;
file = popen("./main63", "w"); if (!file) { printf("fopen failed!\n"); exit(1); }
strcpy(buff, "main62 say hello world!"); cnt = fwrite(buff, sizeof(char), strlen(buff), file); pclose(file);
return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h>
int main() { char buff[1024] = {0};
read(0, buff, sizeof(buff)); printf("main63 recv : %s\n", buff);
return 0; }
|
命名管道 FIFO
- 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信
- 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 命名管道是一种特殊类型的文件。
- 命令创建方式
命名管道的打开规则
- 如果当前打开操作是为了读而打开FIFO时
- O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
- O_NONBLOCK enable:立刻返回成功
- 如果当前打开操作是为写而打开FIFO时
- O_NONBLOCK disbale:阻塞直到有相应进程为读而打开该FIFO
- O_NONBLOCK enable:立刻返回失败,错误码为ENXIO