《Linux 编程 c》学习笔记--管道
管道是半双工的,通常来说,只能在具有相同祖先的进程间使用,例如(父子进程,兄弟进程)
数据读出后,管道中就没有数据了。
管道操作符:|
格式:命令A | 命令B | 命令C... | 命令N
前一个命令的输出作为后一个命令的输入
例如:kill - l | grep SIGKILL
在信号列表中查找SIGKILL
管道结构:
注意!管道位于内核缓冲区,用环形队列来实现
管道有两个文件描述符,一个用于管道输入,一个用于管道输出
父进程通过fork生成子进程,因此子进程可以从父进程那继承到读写管道的描述符:
如果数据从父进程传到子进程,则父进程关闭读描述符, 子进程关闭写描述符。
int pipe(int pipefd[2]);
pipefd[2]为文件描述符数组,其中pipefd[0]是读描述符,pipefd[1]是写描述符,注意一定不能反了!
创建成功返回0,失败返回1
例子1:在一个进程中创建pipe
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd[2];
char writebuf[]="this is a test!\n";
char readbuf[20];
if(pipe(fd)<0){
printf("fail to create pipe!\n");
exit(0);
}
write(fd[1],writebuf,sizeof(writebuf));
read(fd[0],readbuf,sizeof(writebuf));
printf("%s",readbuf);
printf("read descripter f[0] = %d\n",fd[0]);
printf("write descripter f[1] = %d\n",fd[1]);
close(fd[1]);
close(fd[0]);
return 0;
}
输出:
this is a test!
read descripter f[0] = 3
write descripter f[1] = 4
例子2:父子进程中使用管道
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd[2];
char writebuf[] = "parent send to child!\n";
char readbuf[30];
if(pipe(fd)<0){
printf("fail to create pipe!\n");
exit(0);
}
pid_t pid = fork();
if(pid<0){
printf("error!\n");
exit(0);
}
else if(pid ==0){
printf("this is child\n");
close(fd[1]);
read(fd[0],readbuf,sizeof(writebuf));
printf("%s",readbuf);
}
else{
printf("this is parent ..\n");
close(fd[0]);
write(fd[1],writebuf,sizeof(writebuf));
}
return 0;
}
输出:
this is parent ..
this is child
parent send to child!
例子3:兄弟进程中使用管道
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFERSIZE 4096
int main(int argc, char const *argv[])
{
int fd[2];
char buf[BUFFERSIZE];
if(pipe(fd)<0)
perror("pipe failed!\n");
pid_t pid1 = fork();
if(pid1<0){
printf("fork1 error!\n");
exit(0);
}
else if(pid1 == 0){
close(fd[0]);
char buf1[]="hello brother !\n";
strcpy(buf,buf1);
printf("I am child1 sneding info to my brother..\n");
write(fd[1],buf,strlen(buf));
exit(0);
}
pid_t pid2 = fork();
if(pid2<0){
printf("fork2 error\n");
exit(0);
}
else if(pid2 ==0){
close(fd[1]);
read(fd[0],buf,BUFFERSIZE);
printf("I am child2..\n");
printf("len=%d\n",(int)strlen(buf));
printf("child2 recv buf = %s",buf );
exit(0);
}
else{
close(fd[0]);
close(fd[1]);
exit(0);
}
return 0;
}
输出:
I am child1 sneding info to my brother..
I am child2..
len=16
child2 recv buf = hello brother !
例子4:一个进程写管道,两个进程读
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFERSIZE 256
int main(int argc, char const *argv[])
{
int fd[2];
char buf[BUFFERSIZE];
if(pipe(fd)<0)
perror("pipe failed!\n");
pid_t pid1 = fork();
if(pid1<0){
printf("fork1 error!\n");
exit(0);
}
else if(pid1 == 0){
close(fd[0]);
char buf1[]="hello brother !\n";
strcpy(buf,buf1);
printf("I am child1 sneding info to my brother..\n");
write(fd[1],buf,strlen(buf));
printf("pid1 write finished............\n");
close(fd[1]);
exit(0);
}
pid_t pid2 = fork();
if(pid2<0){
printf("fork2 error\n");
exit(0);
}
else if(pid2 ==0){
close(fd[1]);
//sleep(3);
printf("pid2 begin read...............\n");
read(fd[0],buf,BUFFERSIZE);
printf("I am child2 and my recv buf = %s",buf );
exit(0);
}
pid_t pid3 = fork();
if(pid3<0){
printf("fork2 error\n");
exit(0);
}
else if(pid3==0){
close(fd[1]);
printf("pid3 begin read...............\n");
read(fd[0],buf,BUFFERSIZE);
printf("I am child3 and my recv buf = %s",buf );
exit(0);
}
else{
close(fd[0]);
close(fd[1]);
exit(0);
}
return 0;
}
进程pid1写完后关闭写端,有可能是pid2读到buf,也有可能是pid3读到buf。由于写端已经关闭,read不会阻塞!
输出:
I am child1 sneding info to my brother..
pid1 write finished............
pid2 begin read...............
I am child2 and my recv buf = hello brother !
pid3 begin read...............
I am child3 and my recv buf =
我们这里设置的BUFFERSIZE=256
在buf小于BUFFERSIZE的情况下,所有的读操作或者写操作都是原子操作。谁先抢到了谁先读,就是当pid2读到管道数据时,会一次把所有数据读完。所以pid3并没有读到数据。
我们把传输的字符串写得长一些,使其大于BUFFERSIZE:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFERSIZE 256
int main(int argc, char const *argv[])
{
int fd[2];
char buf[BUFFERSIZE];
if(pipe(fd)<0)
perror("pipe failed!\n");
pid_t pid1 = fork();
if(pid1<0){
printf("fork1 error!\n");
exit(0);
}
else if(pid1 == 0){
close(fd[0]);
char buf1[]="hello brother hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh !\n";
strcpy(buf,buf1);
printf("I am child1 sneding info to my brother..\n");
write(fd[1],buf,strlen(buf));
printf("pid1 write finished............\n");
close(fd[1]);
exit(0);
}
pid_t pid2 = fork();
if(pid2<0){
printf("fork2 error\n");
exit(0);
}
else if(pid2 ==0){
close(fd[1]);
//sleep(3);
printf("pid2 begin read...............\n");
read(fd[0],buf,BUFFERSIZE);
printf("I am child2 and my recv buf = %s",buf );
exit(0);
}
pid_t pid3 = fork();
if(pid3<0){
printf("fork2 error\n");
exit(0);
}
else if(pid3==0){
close(fd[1]);
printf("pid3 begin read...............\n");
read(fd[0],buf,BUFFERSIZE);
printf("I am child3 and my recv buf = %s",buf );
exit(0);
}
else{
close(fd[0]);
close(fd[1]);
exit(0);
}
return 0;
}
输出:
I am child1 sneding info to my brother..
pid1 write finished............
pid2 begin read...............
I am child2 and my recv buf = hello brother hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhpid3 begin read...............
I am child3 and my recv buf = hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh !
可以看到pid2读了256B,剩余的友pid3读取。
例子5:两个进程写管道,一个进程读管道
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFERSIZE 256
int main(int argc, char const *argv[])
{
int fd[2];
char buf[BUFFERSIZE];
if(pipe(fd)<0)
perror("pipe failed!\n");
pid_t pid1 = fork();
if(pid1<0){
printf("fork1 error!\n");
exit(0);
}
else if(pid1 == 0){
close(fd[0]);
char buf1[]="hello brother I am pid1 !\n";
strcpy(buf,buf1);
printf("I am child1 sneding info to my brother..\n");
write(fd[1],buf,strlen(buf));
printf("pid1 write finished............\n");
close(fd[1]);
exit(0);
}
pid_t pid2 = fork();
if(pid2<0){
printf("fork2 error\n");
exit(0);
}
else if(pid2 ==0){
close(fd[0]);
char buf1[]="hello brother I am pid2 !";
strcpy(buf,buf1);
printf("I am child2 sneding info to my brother..\n");
write(fd[1],buf,strlen(buf));
printf("pid2 write finished............\n");
close(fd[1]);
exit(0);
}
pid_t pid3 = fork();
if(pid3<0){
printf("fork2 error\n");
exit(0);
}
else if(pid3==0){
close(fd[1]);
printf("pid3 begin read...............\n");
read(fd[0],buf,BUFFERSIZE);
printf("I am child3 and my recv buf = %s",buf );
exit(0);
}
else{
close(fd[0]);
close(fd[1]);
exit(0);
}
return 0;
}
输出:
I am child1 sneding info to my brother..
pid1 write finished............
I am child2 sneding info to my brother..
pid2 write finished............
pid3 begin read...............
I am child3 and my recv buf = hello brother I am pid1 !
hello brother I am pid2 !
命名管道(FIFO)
前面所讨论的管道为匿名管道,存储与内存中,进程之间有亲缘关系
而命名管道,是以文件的新式存于文件系统,进程间不存在亲缘关系,只要路径可访问,就能通过命名管道通信。