一句话总结:管道分无名与有名,无名只能用于父子进程,有名可用于两个相互独立的进程。
1、无名管道
创建管道int pipe(int pipefd[2]); 成功:0;失败:-1
函数调用成功返回r/w两个文件描述符。无需open,但需手动close。规定:fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样。
向管道文件读写数据其实是在读写内核缓冲区。
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤:
1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
从图2可以看到,父进程和子进程都有一对读写fd,每个fd[0]都可以读取到另外两个fd[1]写入管道的数据。
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(void)
{
pid_t pid;
char buf[1024];
int fd[2];
if (pipe(fd) == -1)
sys_err("pipe");
pid = fork();
if (pid < 0) {
sys_err("fork err");
} else if (pid == 0) {
printf("son process\n");
char p[20] = "I am son process\n";
write(fd[1], p, strlen(p));
close(fd[1]); //子进程关闭写端
int len = read(fd[0], buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
close(fd[0]);
printf("son process end\n");
} else {
//sleep(2); //延迟2秒可以清楚看到,子进程写的数据直接被子进程读走了,父进程阻塞在read
printf("parent process\n");
int len = read(fd[0], buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
close(fd[0]); //父进程关闭读端
char p[30] = "I am parent process\n";
write(fd[1], p, strlen(p));
//wait(NULL);
close(fd[1]);
printf("parent process end\n");
}
return 0;
}
运行结果:
zhaojunyandeMacBook-Pro:~ zhaojunyan$ ./pipe
parent process
son process
I am son process
I am parent process
parent process end
son process end
zhaojunyandeMacBook-Pro:~ zhaojunyan$ ./pipe
parent process
son process
I am son process
son process end
^Z
[3]+ Stopped ./pipe
zhaojunyandeMacBook-Pro:~ zhaojunyan$
说明如果刚好父进程读完子进程写入管道的数据后,子进程才开始写数据,则不会出现阻塞;如果子进程抢先将自己写入管道的数据读走了,则父进程就会阻塞在read处。父进程read数据之前加上延时,则结果为:
zhaojunyandeMacBook-Pro:~ zhaojunyan$ ./pipe
son process
I am son process
son process end
parent process,add 2 sec
^Z
[4]+ Stopped ./pipe
zhaojunyandeMacBook-Pro:~ zhaojunyan$
全双工通信要特别注意,下面是半双工例子(写两次数据,read时一次全部读出来):
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(void)
{
pid_t pid;
char buf[1024];
int fd[2];
if (pipe(fd) == -1)
sys_err("pipe");
pid = fork();
if (pid < 0) {
sys_err("fork err");
} else if (pid == 0) {
close(fd[1]); //子进程关闭写端
int len = read(fd[0], buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
close(fd[0]);
} else {
close(fd[0]); //父进程关闭读端
char p[30] = "I am parent process\n";
write(fd[1], p, strlen(p));
write(fd[1], p, strlen(p));
close(fd[1]);
}
return 0;
}
2、有名管道
int mkfifo(const char *pathname, mode_t mode)
pathname: FIFO文件名
mode: 属性
一旦创建了了FIFO,就可open去打开它,可以使用open,read,close等去操作FIFO
当打开FIFO时,非阻塞标志(O_NONBLOCK)将会对读写产生如下影响:
(1)没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如试图读取空的FIFO,将导致进程阻塞;
(2)使用O_NONBLOCK:访问要求无法满足时不阻塞,立即出错返回,errno是ENXIO;
有名管道的总体操作:
创建管道mkfifo
打开管道open
读管道read
写管道write
关闭管道close
删除管道unlink
FIFO文件在使用上和普通文件的不同之处:
1. 读取fifo文件的进程只能以”RDONLY”方式打开fifo文件。
2. 写fifo文件的进程只能以”WRONLY”方式打开fifo
3. fifo文件里面的内容被读取后,就消失了。但是普通文件里面的内容读取后还存在。
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> //这个很重要,要不open、read找不到
#define FIFO "/home/zjy/Documents/myfifo"
int main()
{
char buf_r[100];
int fd;
int nread;
/* 创建管道 */
if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
printf("cannot create fifoserver\n");
printf("Preparing for reading bytes...\n");
/* 打开管道 */
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0); //只读、非阻塞
if(fd==-1)
{
perror("open");
exit(1);
}
while(1)
{
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100))==-1)
{
if(errno==EAGAIN)
printf("no data yet\n");
}
else if(nread > 0)
printf("read %s from FIFO\n",buf_r);
sleep(1);
}
pause(); /*暂停,等待信号*/
unlink(FIFO); //删除文件
return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define FIFO_SERVER "/home/zjy/Documents/myfifo"
int main()
{
int fd;
char w_buf[100] = "hello world";
int nwrite;
/*打开管道*/
fd=open(FIFO_SERVER, O_WRONLY|O_NONBLOCK, 0); //只写,非阻塞
/* 向管道写入数据 */
if((nwrite=write(fd,w_buf,100))==-1)
printf("The FIFO has not been read yet.Please try later\n");
else
printf("write %s to the FIFO\n",w_buf);
return 0;
}
运行结果:
第一次:先运行fifo_read,程序阻塞,直到运行fifo_write有数据写入管道;
第二次:先运行fifo_write,直接将数据写入管道后退出,然后运行fifo_read可以将数据读出来。如果read端open时不加o_nonblock,则在再次运行fifo_write之前是无法获取到之前的信息的。