概念
- 管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
比如:eg: ls | wc -l ps –u wbm01|grep “aa”
管道的本质====》固定大小的内核缓冲区
- 管道限制
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
匿名管道pipe
- 包含头文件
<unistd.h>
- 功能:创建一无名管道
- 原型
int pipe(int fd[2]);
- 参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端 - 返回值:成功返回0,失败返回错误代码
管道创建后示意图
管道的读写规则
- 当没有数据可读时
- O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
- O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
- 当管道满的时候
- O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
- O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
- 如果所有管道写端对应的文件描述符被关闭,则read返回0
- 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
示例代码
- 默认阻塞读和SIGPIPE写
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(void )
{
int pipefd[2];
pid_t pid;
//创建管道文件,并打开
if (pipe(pipefd) == -1 )
{
printf("pipe() err..\n");
return -1;
}
pid = fork();
if (pid == -1)
{
printf("fork err..\n");
return -1;
}
if (pid == 0)
{
close(pipefd[0]);
write(pipefd[1], "hello hello....", 6);
close(pipefd[1]);
printf("child .....quit\n");
exit(0);
}
else if (pid > 0 )
{
int len = 0;
char buf[100] = {0};
close(pipefd[1]);
len = read(pipefd[0], buf, 100);
printf("len:%d, buf:%s \n", len , buf);
close(pipefd[0]);
}
wait(NULL);
printf("parent ..quit\n");
return 0;
}
- 使用fcntl修改为非阻塞模式
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
//非阻塞场景
int main(void )
{
int ret = 0;
int pipefd[2];
ret = pipe(pipefd);
if (ret < 0)
{
perror("ddd");
exit(0);
}
int pid;
pid = fork();
if (pid == -1)
{
perror("dd");
exit(0);
}
if (pid == 0) //子进程
{
close(pipefd[0]);
sleep(5);
write(pipefd[1], "childaaaaaaaaa", 6 );
printf("chile quit\n");
close(pipefd[1]);
exit(0);
}
close(pipefd[1]);
char readbuf[1000] = {0};
int n = 0;
int flags = fcntl(pipefd[0], F_GETFL);
flags = flags | O_NONBLOCK;
ret = fcntl(pipefd[0], F_SETFL, flags);
if (ret == -1)
{
printf("fcntl err.\n");
exit(0);
}
n = read(pipefd[0], readbuf,sizeof(readbuf));
if (n < 0)
{
printf("read no block--return right now\n");
}
printf("%s\n", readbuf);
close(pipefd[0]);
wait(NULL);
printf("parent quit\n");
return 0;
}
- 所有的写端都关闭–read返回0,并非错误
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(void )
{
int ret = 0;
int pipefd[2];
ret = pipe(pipefd);
if (ret < 0)
{
perror("ddd");
exit(0);
}
int pid;
pid = fork();
if (pid == -1)
{
perror("dd");
exit(0);
}
if (pid == 0) //子进程
{
close(pipefd[0]);//关闭子进程的读端
close(pipefd[1]);//关闭子进程的写端
exit(0);
}
close(pipefd[1]);关闭父进程的写端
char readbuf[1000] = {0};
int n = 0;
sleep(3);//保证子进程先结束--确保所有的写端都关闭
n = read(pipefd[0], readbuf,sizeof(readbuf));
printf("all write port closed,num of read:%d \n", n);
printf("%s\n", readbuf);
close(pipefd[0]);
wait(NULL);
printf("parent quit\n");
return 0;
}
- 两端的读都被关闭,写操作会发生一个错误信号 SIGPIPE 管道断开的错误信号
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void myhandle(int sig)
{
printf("recv sig:%d \n", sig);
}
//演示管道两端的读都被关闭,写操作会发生一个错误信号 SIGPIPE 管道断开的错误信号
int main(void )
{
int pipefd[2];
pid_t pid;
//注册管道处理函数
signal(SIGPIPE, myhandle);
if (pipe(pipefd) == -1 )
{
printf("pipe() err..\n");
return -1;
}
pid = fork();
if (pid == -1)
{
printf("fork err..\n");
return -1;
}
if (pid == 0)
{
close(pipefd[0]); //关闭子进程读端
printf("child .....quit\n");
exit(0);
}
else if (pid > 0 )
{
int len = 0;
close(pipefd[0]); //关闭父进程读端
//父进程sleep 1s后,再读
sleep(1);
len = write(pipefd[1], "wangbaomingwangbaoming", 6);
//会有信号发生(异步通知事件发生)言外之意主程序可以随时被打断
//有了信号,就可以支持这种机制
if (len == -1)
{
printf("两端的读都关闭,会发送信号,然后再执行这句话 :%d \n", len);
}
}
wait(NULL);//避免僵尸进程
printf("parent ..quit\n");
return 0;
}
- 测试管道容量
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
//测试管道容量
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void myhandle(int sig)
{
printf("recv sig:%d \n", sig);
}
int main(void )
{
int pipefd[2];
pid_t pid;
//注册管道处理函数
signal(SIGPIPE, myhandle);
if (pipe(pipefd) == -1 )
{
printf("pipe() err..\n");
return -1;
}
pid = fork();
if (pid == -1)
{
printf("fork err..\n");
return -1;
}
if (pid == 0)
{
int count = 0;
int ret = 0;
close(pipefd[0]);
//写端变成非阻塞
int flags = fcntl(pipefd[1], F_GETFL);
flags = flags | O_NONBLOCK;
ret = fcntl(pipefd[1], F_SETFL, flags);
if (ret == -1)
{
printf("fcntl err.\n");
exit(0);
}
while(1)
{
ret = write(pipefd[1] , "a", 1); //,非阻塞模式下:写满的时候再继续写入会立即返回-1
if (ret == -1)
{
perror("write pipe");
break;
}
count ++;
}
printf("count:%d \n\n", count);
close(pipefd[1]);
exit(0);
}
else if (pid > 0 )
{
sleep(4);
close(pipefd[0]);
close(pipefd[1]);
}
wait(NULL);
printf("parent ..quit\n");
return 0;
}
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
#define TEST_SIZE 68*1024 //68K
int main(void)
{
char a[TEST_SIZE];
char b[TEST_SIZE];
memset(a, 'A', sizeof(a));
memset(b, 'B', sizeof(b));
int pipefd[2];
int ret = pipe(pipefd);
if (ret == -1)
ERR_EXIT("pipe error");
pid_t pid;
pid = fork();
if (pid == 0) //A子进程写68K数据A
{
close(pipefd[0]);
ret = write(pipefd[1], a, sizeof(a));
printf("apid=%d write %d bytes to pipe\n", getpid(), ret);
exit(0);
}
pid = fork();
if (pid == 0) //B子进程写68K数据B
{
close(pipefd[0]);
ret = write(pipefd[1], b, sizeof(b));
printf("bpid=%d write %d bytes to pipe\n", getpid(), ret);
exit(0);
}
close(pipefd[1]);
sleep(1);
int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
char buf[1024*4] = {0};
int n = 1;
while (1)
{
//父进程4k 4k的读数据,发现AB进程是交叉的写数据到管道。
//多个进程往管道里面,写数据。
ret = read(pipefd[0], buf, sizeof(buf));
if (ret == 0)
break;
printf("n=%02d pid=%d read %d bytes from pipe buf[4095]=%c\n", n++, getpid(), ret, buf[4095]);
write(fd, buf, ret);
}
return 0;
}
- 管道概念再深入–模仿
ls | wc -w
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
#define TEST_SIZE 68*1024 //68K
int main(void )
{
int pipefd[2];
pid_t pid;
if (pipe(pipefd) == -1 )
{
printf("pipe() err..\n");
return -1;
}
pid = fork();
if (pid == -1)
{
printf("fork err..\n");
return -1;
}
if (pid == 0)
{
close(pipefd[0]);
//复制文件描述符pipefd[1],给标准输出,言外之意:execlp的ls命令输出到管道中
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[1]);
execlp("ls", "ls", NULL);
//如果替换新的进程印象失败,则会执行下面一句话
fputs("execute the cmd ls err..\n",stderr);
exit(0);
}
else if (pid > 0 )
{
int len = 0;
char buf[100] = {0};
close(pipefd[1]);
//复制文件描述符pipefd[0],给标准输入,言外之意:execlp的wc命令从管道中读
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
execlp("wc", "wc", "-w", NULL);
printf("len:%d, buf:%s \n", len , buf);
}
wait(NULL);
printf("parent ..quit\n");
return 0;
}