1.管道的概念
本质:
- 内核缓冲区
- 伪文件 - 不占用磁盘空间
特点:
- 两部分:读端,写端,对应两个文件描述符
- 数据写端流入, 读端流出
- 操作管道的进程被销毁之后,管道自动被释放了
- 管道默认是阻塞的,读写两端都是
2.管道的原理
内部实现方式:环形队列
特点:先进先出
缓冲区大小:
默认4k,根据命令ulimit -p查看
大小会根据实际情况做适当调整
3. 管道的局限性
- 队列:数据只能读取一次,不能重复读取
- 半双工:单向数据流
- 匿名管道:适用于有血缘关系的进程
4.创建匿名管道
int pipe(int fd[2]);
fd- 传出参数
fd[0] - 读端
fd[1] - 写端
5.管道的读写行为
读操作
有数据
read(fd) - 正常读,返回读出的字节数
无数据
写端全部关闭
read解除阻塞,返回0
相当于读文件读到了尾部
没有全部关闭
read阻塞
写操作
读端全部关闭
管道破裂,进程被终止
内核给当前进程发信号SIGPIPE
读端没全部关闭
缓冲区写满了,write 阻塞
缓冲区没有满,write继续写
父子间进程通信
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
/*
功能:父子间进行通信,实现ps aux | grep bash
自己通过代码进行实现的话,首先需要一个管道和两个进程
ps aux把数据写入管道,grep bash从管道读取数据
*/
int main(int argc, const char* argv[])
{
//需要管道
int fd[2];
int ret=pipe(fd);
if(ret==-1)
{
perror("pipe error");
exit(1);
}
//创建子进程
pid_t pid=fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
//ps aux | grep bash
//父进程 ps aux,写管道,关闭终端
if(pid>0)
{
close(fd[0]);
//数据默认输出到终端,现在要让数据写到管道里面去
//数据写到管道,STDOUT_FILENO指向管道的写端
dup2(fd[1],STDOUT_FILENO);
execlp("ps","ps","aux",NULL);
perror("execlp ps");
exit(1);
}
//子进程 grep bash 从管道中搜索 读管道 关闭写端
else if(pid==0)
{
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
execlp("grep","grep","bash",NULL);
perror("execlp grep");
exit(1);
}
return 0;
}
兄弟间进程通信
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, const char* argv[])
{
int fd[2];
int ret = pipe(fd);
if(ret == -1)
{
perror("pipe error");
exit(1);
}
printf("fd[0] = %d\n", fd[0]);
printf("fd[1] = %d\n", fd[1]);
int i = 0;
int num = 2;
for(; i<num; ++i)
{
pid_t pid = fork();
if(pid == 0)
{
break;
}
}
// 通过i的取值判断父进程和两个子进程
if(i == num)
{
//父进程关闭读写两端
close(fd[0]);
close(fd[1]);
pid_t wpid;
while( (wpid = waitpid(-1, NULL, WNOHANG)) != -1 )
{
if(wpid == 0)
{
continue;
}
printf("died child pid = %d\n", wpid);
}
}
else if(i == 0)
{
// ps aux
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ps", "ps", "aux", NULL);
}
else if(i == 1)
{
// grep bash
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("grep", "grep", "bash", NULL);
}
return 0;
}
设置管道的非阻塞属性
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, const char* argv[])
{
int fd[2];
int ret=pipe(fd);
int flags=fcntl(fd[0],F_GETFL);//先获取原来的flags
flags |=O_NONBLOCK;//设置新的flags
fcntl(fd[0],F_SETFL,flags);
if(ret==-1)
{
perror("pipe error");
exit(1);
}
printf("pipe[0]=%d\n",fd[0]);
printf("pipe[1]=%d\n",fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
FIFO
管道没有名字,因此它们的最大劣势是只能用于有一个共同祖先进程的各个进程之间。我们无法在无亲缘关系的两个进程之间创建一个管道并将它用作IPC管道。
FIFO类似于管道,也叫做有名管道。是一个单向(半双工)数据流。不同于管道的是,每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO。
1.特点
在磁盘上有这样一个文件,通过ls -l能够查看,但在磁盘大小永远为0
在内核中有一个对应的缓冲区
半双工的通信方式
2.创建方式
一种是通过命令创建
mkdido 管道名
另一种是通过函数进行创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
成功返回0,失败-1.
利用FIFO进行客户端与服务端的通信
服务端
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
//服务端
int main(int argc, const char* argv[])
{
if(argc<2)
{
printf("./a.out fifoname\n");
exit(1);
}
//判断文件是否存在
int ret=access(argv[1],F_OK);
if(ret==-1)
{
int r=mkfifo(argv[1],0664);
if(r==-1)
{
perror("mkfifo error");
exit(1);
}
printf("有名管道创建成功,可以开始通信了\n");
}
int fd=open(argv[1],O_RDONLY);
if(fd==-1)
{
perror("open error");
exit(1);
}
char recvbuf[1024];
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
int ret=read(fd,recvbuf,sizeof(recvbuf));
fputs(recvbuf,stdout);
}
close(fd);
}
客户端
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
//客户端
int main(int argc, const char* argv[])
{
if(argc<2)
{
printf("./a.out fifoname\n");
exit(1);
}
//判断文件是否存在
int ret=access(argv[1],F_OK);
if(ret==-1)
{
int r=mkfifo(argv[1],0664);
if(r==-1)
{
perror("mkfifo error");
exit(1);
}
printf("有名管道%s创建成功\n",argv[1]);
}
int fd=open(argv[1],O_WRONLY);
if(fd==-1)
{
perror("open error");
exit(1);
}
char sendbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
write(fd,sendbuf,strlen(sendbuf));
memset(&sendbuf,0,sizeof(sendbuf));
}
close(fd);
return 0;
}