# unamed pipe
既然可以通过 “文件描述符” 来操作管道,那么他就是一个文件(管道文件),但无名管道文件比较特殊,他没有文件名。
# 函数原型
#include "unistd.h" | |
int piep(int fd[2]); |
# 功能
创建一个用于亲缘进程(父子进程)之间通信的无名管道(缓存),并将管道与两个读写文件描述符关联起来。
无名管道只能用于亲缘进程间通信,为什么只能用于亲缘进程之间通信呢?
因为,没有文件名,进程没有办法通过 open
打开管道文件,从而得到文件描述符,所以只有一种办法,那就是父进程先调用 pipe
创建出管道,并得到读写管道的文件描述符;然后,再 fork
出子进程,让子进程通过继承父进程打开的文件描述符,父子进程就能操作同一个管道,从而实现通信。
读管道时,如果没有数据的话,读操作会被休眠(阻塞)
# 参数
缓存地址,缓存用于存放读写管道的文件描述符
这个缓存就是拥有两个元素的 int
型数组
元素[0]
: 里面放的是读管道的读文件描述符
元素[1]
: 里面放的是写管道的写文件描述符
返回值
:成功返回 0
,失败返回 -1
,并且 errno
被设置
# 单向通信
代码演示:
实现过程
父进程在
fork
之前先调用pipe
创建无名管道,获取读写文件描述符fork
创建出子进程,子进程继承无名管道读写文件描述符父子进程使用各自管道的读写文件描述符进行读写操作,即可实现通信
// 现在实现父进程写,子进程读 | |
#include "stdio.h" | |
#include "stdlib.h" | |
#include "unistd.h" | |
#include "strings.h" | |
int main(void) | |
{ | |
// 调用 pipe 创建无名管道,获取文件描述符 | |
int ret=0; // 接受函数返回值 | |
int fd[2];// 文件描述符 | |
int pipe(int fd[2]); | |
ret=pipe(fd); | |
if(ret==-1){ | |
printf("pipe error"); | |
exit(-1); | |
} | |
// 创建子进程 | |
ret=fork(); | |
if(ret>0) | |
{ | |
close(fd[0]); | |
while(1) | |
{ | |
write(fd[1],"hello",5); | |
sleep(1); | |
} | |
}else if(ret == 0){ | |
close(fd[1]); | |
while(1) | |
{ | |
char childbuf[30]; | |
bzero(childbuf,sizeof(childbuf));// 清空缓冲区 | |
read(fd[0],childbuf,sizeof(childbuf)); | |
printf("child receive the data:%s\n",childbuf); | |
} | |
} | |
return 0; | |
} |
// 外教给的 zz 例子 | |
#include "stdio.h" | |
#include "stdlib.h" | |
#include "unistd.h" | |
#include "string.h" | |
#include "sys/wait.h" | |
int main(void) | |
{ | |
int pid,child_pid,status; | |
int fd[2],num_bytesRead; | |
//create pipe | |
pipe(fd); | |
printf("I'm main,my PID is:%d\n",getpid()); | |
pid=fork(); | |
//child process | |
if(pid==0){ | |
while(1){ | |
printf("child:%d,my parent:%d\n",getpid(),getppid()); | |
char childbuf[200]; | |
strcpy(childbuf,"i am child!"); | |
close(fd[0]); | |
write(fd[1],childbuf,200); | |
sleep(3); | |
close(fd[1]); | |
} | |
}else{ | |
while(1){ | |
char mybuf[200]; | |
close(fd[1]); | |
num_bytesRead=read(fd[0],mybuf,200); | |
sleep(3); | |
close(fd[0]); | |
printf("got message from child, %s\n",mybuf); | |
child_pid=wait(&status); | |
} | |
} | |
return 0; | |
} |
一共三句话: | |
I'm main,my PID is:%d\n",getpid() | |
child:%d,my parent:%d\n",getpid(),getppid() | |
got message from child, %s\n",mybuf |
其中只有 2,3 在循环里,分别对应父子进程,而结果显示是最后只有父进程了,孩子没了,这也印证了 wait
的作用
# SIGPIPE 信号
为什么会产生这个信号?
写管道时,如果管道的读端被 close
的话,向管道 “写” 数据的进程会被内核发送一个 SIGPIPE
信号,发这个信号的目的就是通知你,管道所有的 “读” 都被关闭了。这就好比,别人把水管的出口(读端)堵住了,你还往里面一直灌水(写),别人会警告你,不然管道就被你弄坏了。只有管道的所有读端都被关闭,才会出现这个信号,只要还有一个读端开着,就不会产生它。
// 我们改一下前面的代码,在关闭子进程写端的基础上,关闭读端 | |
#include "stdio.h" | |
#include "stdlib.h" | |
#include "unistd.h" | |
#include "strings.h" | |
int main(void) | |
{ | |
// 调用 pipe 创建无名管道,获取文件描述符 | |
int ret=0; // 接受函数返回值 | |
int fd[2];// 文件描述符 | |
int pipe(int fd[2]); | |
ret=pipe(fd); | |
if(ret==-1){ | |
printf("pipe error"); | |
exit(-1); | |
} | |
// 创建子进程 | |
ret=fork(); | |
if(ret>0) | |
{ | |
close(fd[0]); | |
while(1) | |
{ | |
write(fd[1],"hello",5); | |
sleep(1); | |
} | |
}else if(ret == 0){ | |
close(fd[1]); | |
close(fd[0]); | |
while(1) | |
{ | |
char childbuf[30]; | |
bzero(childbuf,sizeof(childbuf));// 清空缓冲区 | |
read(fd[0],childbuf,sizeof(childbuf)); | |
printf("child receive the data:%s\n",childbuf); | |
} | |
} | |
return 0; | |
} |
尝试运行
下图是没有关闭子进程读端的情况,观察右图,发现创建了两个 test
,一个是子进程,一个是父进程
关闭了子进程读端之后, ps -a
发现只创建了一个 test
,这是因为子进程关闭了读端时,父进程尝试往管道中写入,被内核发送了 sigpipe
信号阻止了
# 双向通信
用无名管道实现双向通信,有这样一个问题,由于双向的读写同时进行,同一侧的写会被读抢先读走,所以,需要两个管道。
// 通过无名管道创建双向通信 | |
#include "stdio.h" | |
#include "stdlib.h" | |
#include "unistd.h" | |
#include "strings.h" | |
int main(void) | |
{ | |
// 调用 pipe 创建无名管道,获取文件描述符 | |
int ret=0; // 接受函数返回值 | |
int fd1[2];// 文件描述符 | |
int fd2[2]; | |
int pipe(int fd1[2]); | |
int pipe(int fd2[2]); | |
ret=pipe(fd1); | |
if(ret==-1){ | |
printf("pipe error"); | |
exit(-1); | |
} | |
ret=pipe(fd2); | |
if(ret==-1){ | |
printf("pipe error"); | |
exit(-1); | |
} | |
// 创建子进程 | |
ret=fork(); | |
if(ret>0) | |
{ | |
close(fd1[0]); | |
close(fd2[1]); | |
char mybuf[30]; | |
while(1) | |
{ | |
write(fd1[1],"hello",5); | |
sleep(1); | |
bzero(mybuf,sizeof(mybuf));// 清空缓冲区 | |
read(fd2[0],mybuf,sizeof(mybuf)); | |
printf("parent got the data:%s\n",mybuf); | |
} | |
}else if(ret == 0){ | |
close(fd1[1]);// 对于管道 1,父进程不需要写端,关闭写端 | |
close(fd2[0]);// 对于管道 2,父进程不需要读端,关闭读端 | |
char childbuf[30]; | |
while(1) | |
{ | |
bzero(childbuf,sizeof(childbuf));// 清空缓冲区 | |
read(fd1[0],childbuf,sizeof(childbuf)); | |
printf("child receive the data:%s\n",childbuf); | |
sleep(1); | |
write(fd2[1],"world",5); | |
} | |
} | |
return 0; | |
} |
通过以上的例子,我们不难发现,无名管道的缺点:
无法用于非亲缘进程之间
因为非亲缘进程之间没办法继承管道的文件描述符
无法实现多进程之间的网状通信
如果非要使用无名管道实现多进程之间的网状通信,文件描述符的继承关系 将非常的复杂,所以无名管道基本上知识和两个进程之间的通信
适用情况:
如果通信的进程只有两个,而且还是亲缘进程时
- 直接继承父子进程之间的通信
- 间接继承关系的两进程之间的通信
# named pipe
相反的,如果有文件名,就是有名管道;也就是说我们调用相应的 API 创建好 “有名管道” 后,会在相应的路径下看到一个有名字的文件;但不管有名与否,本质上就是通过共享操作这段缓存来实现,只不过这段操作缓存的方式,是以读写文件的形式来操作的。
# 函数原型
#include "sys/types.h" | |
#include "sys/stat.h" | |
int mkfifo(const char *pathname,mode_t mode); |
# 功能
创建有名管道文件,创建好后便可 open 打开,如果是创建普通文件的话,我们可以使用 open
的 O_CREAT
选项来创建,比如:
open("./file",O_RDWR|O_CREAT,0664);
但是对于 “有名管道” 这种特殊文件,只能使用 mkfifo
函数来创建。
较于无名管道,它可以用于非亲缘进程之间的通信
因为有文件名,所以进程可以直接调用 open 函数打开文件,从而得到文件描述符,不需要通过无名管道一样,必须通过继承的方式才能获取到文件描述符。
读管道时,如果管道没有数据的话,读操作同样会被阻塞(休眠)
当进程写一个所有读端都被关闭的管道时,进程会被内核返回 sigpipe
信号,如果不想被该信号终止的话,我们需要忽略、捕获、屏蔽该信号。
# 参数
pathname
: 被创建管道文件的文件路径名
mode
: 指定被创建时原始权限,一般为 0664(110110100)
,必须包含读写权限
返回值
:成功返回 0
,失败返回 -1
,并且 errno
被设置
# 大致步骤
进程待用
mkfifo
创建有名管道open
打开有名管道read/write
读写管道进行通信
对于通信的两个进程来说,创建管道时,只需要一个人创建,另一个人直接使用即可;为了保证管道一定被创建,最好是两个进程都包含创建管道的代码,谁先运行就谁先创建,后运行的发现管道已经被创建好了,那就直接 open
打开使用。
# 单向通信
#define FIFONAME1 "./fifo1" | |
// 首先创建 pipe 管道文件 | |
int create_open_fifo(char *fifoname,int open_mode){ | |
int ret = -1; | |
int fd = -1; | |
ret = mkfifo(fifoname,0664); | |
if(ret == -1 && errno!=EEXIST){ // 如果不是因为 "已经存在了" 这个问题,你才去报错 | |
printf("mkfifo error"); | |
exit(-1); | |
} | |
fd = open(fifoname,open_mode); | |
if(fd == -1){ | |
printf("open fail"); | |
exit(-1); | |
} | |
return fd; | |
} | |
int main() | |
{ | |
char buf[100]; | |
int ret = -1; | |
int fd = -1; | |
fd = create_open_fifo(FIFONAME1,O_WRONLY); | |
while(1) | |
{ | |
bzero(buf,sizeof(buf)); | |
scanf("%s",buf); | |
write(fd,buf,sizeof(buf)); | |
} | |
return 0; | |
} |
这里有个小问题,创建一个管道文件,结束后没用了也没删掉。
由于我们手动结束程序的方法基本上是 CTRL+C
,直接关闭,它作为程序终止的信号 SIGINT
,我们可以使用 signal
函数制作一个函数来处理这个信号
#define FIFONAME1 "./fifo1" | |
// 寻找终止信号,发现即删掉 FIFONAME1 并退出 | |
void signal_fun(int signo) | |
{ | |
//unlink(); | |
remove(FIFONAME1); | |
exit(-1); | |
} | |
int main() | |
{ | |
char buf[100]; | |
int ret = -1; | |
int fd = -1; | |
signal(SIGINT,signal_fun); | |
fd = create_open_fifo(FIFONAME1,O_WRONLY); // 只写地方式创建并打开 FIFONAME1 | |
while(1) | |
{ | |
bzero(buf,sizeof(buf)); | |
scanf("%s",buf); | |
write(fd,buf,sizeof(buf)); | |
} | |
return 0; | |
} |
//serve.c (client.c 稍作修改) | |
#define FIFONAME1 "./fifo1" | |
// 创建 pipe 管道文件 | |
int create_open_fifo(char *fifoname,int open_mode){ | |
int ret = -1; | |
int fd = -1; | |
ret = mkfifo(fifoname,0664); | |
if(ret == -1 && errno!=EEXIST){ | |
printf("mkfifo error"); | |
exit(-1); | |
} | |
fd = open(fifoname,open_mode); | |
if(fd == -1){ | |
printf("open fail"); | |
exit(-1); | |
} | |
return fd; | |
} | |
void signal_fun(int signo) | |
{ | |
//unlink(); | |
remove(FIFONAME1); | |
exit(-1); | |
} | |
int main() | |
{ | |
char buf[100]; | |
int ret = -1; | |
int fd = -1; | |
signal(SIGINT,signal_fun); | |
fd = create_open_fifo(FIFONAME1,O_RDONLY); // 只读地方式创建并打开 FIFONAME1 | |
while(1) | |
{ | |
bzero(buf,sizeof(buf)); | |
read(fd,buf,sizeof(buf)); | |
printf("recv:%s\n",buf); | |
} | |
return 0; | |
} |
//head.h | |
#include "unistd.h" | |
#include "strings.h" | |
#include "signal.h" | |
#include "sys/types.h" | |
#include "sys/stat.h" | |
#include "fcntl.h" // 需要用到 open 函数 | |
#include "stdlib.h" | |
#include "stdio.h" | |
#include "errno.h" | |
#include "signal.h" |
双客户自然也可以实现
外教的 zz 例子
//client.c | |
#include "header.h" | |
int main() | |
{ | |
int fdout,status; | |
char message[80]; | |
status=mkfifo(PIPE_1,0666); | |
if (( status == -1) && (errno != EEXIST)) { | |
perror("Error creating the named pipe - remove pipe if already exists"); | |
exit (1); | |
} | |
sprintf(message,"This is a client,my pid is:%d\n",getpid()); | |
if((fdout=open(PIPE_1,O_WRONLY))==-1) | |
{ | |
perror("./fifo1"); | |
exit(-1); | |
} | |
write(fdout,message,sizeof(message)); | |
close(fdout); | |
return 0; | |
} |
//serve.c | |
#include "header.h" | |
int main() | |
{ | |
int i,fdin,status,count,numRead; | |
char buff[BUF_SIZE]; | |
status=mkfifo(PIPE_1,0666); | |
if((status==-1)&&(errno!=EEXIST)) | |
{ | |
perror("Error creating the named pipe - remove pipe if already exist"); | |
exit(1); | |
} | |
fdin=open(PIPE_1,O_RDONLY); | |
numRead=read(fdin,buff,BUF_SIZE); | |
if(numRead==0) | |
{ | |
printf("PIPE_1 write side closed,server now exits\n"); | |
exit(-1); | |
} | |
printf("got this string from pipe %s\n",buff); | |
for(i=0;i<sizeof(buff);i++) | |
{ | |
if(islower(buff[i])) | |
buff[i]=toupper(buff[i]); | |
} | |
printf("now converted uppercase string %s\n",buff); | |
close(fdin); | |
unlink(PIPE_1); | |
return 0; | |
} |
//head.h | |
#include "stdlib.h" | |
#include "stdio.h" | |
#include "fcntl.h" | |
#include "unistd.h" | |
#include "errno.h" | |
#include "sys/types.h" | |
#include "sys/stat.h" | |
#include "strings.h" | |
#define PIPE_1 "./fifo2" | |
#define BUF_SIZE 256 |
# 双向通信
与无名管道的双向通道相似,由于双向的读写同时进行,同一侧的写会被读抢先读走,所以,需要两个管道,各司其职。
//client.c | |
#include "head.h" | |
// 创建 pipe 管道文件 | |
int create_open_fifo(char *fifoname,int open_mode){ | |
int ret = -1; | |
int fd = -1; | |
ret = mkfifo(fifoname,0664); | |
if(ret == -1 && errno!=EEXIST){ | |
printf("mkfifo error"); | |
exit(-1); | |
} | |
fd = open(fifoname,open_mode); | |
if(fd == -1){ | |
printf("open fail"); | |
exit(-1); | |
} | |
return fd; | |
} | |
void signal_fun(int signo) | |
{ | |
//unlink(); | |
remove(FIFONAME1); | |
exit(-1); | |
} | |
int main(void) | |
{ | |
char buf[100]={0}; | |
int ret = -1; | |
int fd1 = -1; | |
int fd2 = -1; | |
fd1 = create_open_fifo(FIFONAME1,O_WRONLY); // 只写地方式创建并打开 FIFONAME1 | |
fd2 = create_open_fifo(FIFONAME2,O_RDONLY); | |
ret=fork(); | |
if(ret>0) | |
{ | |
signal(SIGINT,signal_fun); | |
while(1) | |
{ | |
bzero(buf,sizeof(buf)); | |
scanf("%s",buf); | |
write(fd1,buf,sizeof(buf)); | |
} | |
}else if(ret==0){ | |
while(1) | |
{ | |
bzero(buf,sizeof(buf)); | |
read(fd2,buf,sizeof(buf)); | |
printf("recv:%s\n",buf); | |
} | |
} | |
/*while(1) | |
{ | |
bzero(buf,sizeof(buf)); | |
scanf("%s",buf); | |
write(fd1,buf,sizeof(buf)); | |
read(fd2,buf,sizeof(buf)); | |
}*/ | |
return 0; | |
} |
//serve.c | |
#include "head.h" | |
// 创建 pipe 管道文件 | |
int create_open_fifo(char *fifoname,int open_mode){ | |
int ret = -1; | |
int fd = -1; | |
ret = mkfifo(fifoname,0664); | |
if(ret == -1 && errno!=EEXIST){ | |
printf("mkfifo error"); | |
exit(-1); | |
} | |
fd = open(fifoname,open_mode); | |
if(fd == -1){ | |
printf("open fail"); | |
exit(-1); | |
} | |
return fd; | |
} | |
void signal_fun(int signo) | |
{ | |
//unlink(); | |
remove(FIFONAME1); | |
exit(-1); | |
} | |
int main(void) | |
{ | |
char buf[100]={0}; | |
int ret = -1; | |
int fd1 = -1; | |
int fd2 = -1; | |
fd1 = create_open_fifo(FIFONAME1,O_RDONLY); // 只读地方式创建并打开 FIFONAME1 | |
fd2 = create_open_fifo(FIFONAME2,O_WRONLY); // 只写地方式创建并打开 FIFONAME2 | |
ret = fork(); | |
if(ret>0) | |
{ | |
signal(SIGINT,signal_fun); | |
while(1) | |
{ | |
bzero(buf,sizeof(buf)); | |
read(fd1,buf,sizeof(buf)); | |
printf("recv:%s\n",buf); | |
} | |
}else if(ret==0){ | |
while(1) | |
{ | |
bzero(buf,sizeof(buf)); | |
scanf("%s",buf); | |
write(fd2,buf,sizeof(buf)); | |
} | |
} | |
/*while (1) 这里有个问题,读操作会把写操作阻塞 | |
{ | |
bzero (buf,sizeof (buf)); | |
read (fd1,buf,sizeof (buf)); | |
printf ("recv:% s\n",buf); | |
write (fd2,buf,sizeof (buf)); | |
}*/ | |
return 0; | |
} |
//head.h | |
#include "unistd.h" | |
#include "strings.h" | |
#include "signal.h" | |
#include "sys/types.h" | |
#include "sys/stat.h" | |
#include "fcntl.h" // 需要用到 open 函数 | |
#include "stdlib.h" | |
#include "stdio.h" | |
#include "errno.h" | |
#include "signal.h" | |
#define FIFONAME1 "./fifo1" | |
#define FIFONAME2 "./fifo2" |
可以实现握手!