前言:管道是用于进程之间通信的方式,本质是在内存中建立一段缓存区,并且这段缓存区有固定大小,不能超出。有名管道是任意进程之间都可以进行通信的,并且可以以文件的形式存在的,而无名管道只能用于父子进程之间的通信,同时也只在其生命周期内存在。
一、无名管道
通常情况下,无名管道默认是单向的,用于父进程向子进程发送文件描述符等数据,但实际上也是可以双向传递的。无名管道的创建需要用到系统调用pipe();
函数头文件:#include <unistd.h>
函数定义:int pipe(int pipefd[2]);
函数返回值: 成功时返回 0。
失败时返回 -1,并设置 errno 以指示错误。
该函数需要传入一个长度为二的数组,之后,pipefd[0]就作为读端文件描述符,pipefd[1]:作为写端文件描述符。通过文件I/O函数就可以在管道中进行读写操作。
来看一段代码:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
int main()
{
char buf_w[128] = "";//定义两个数组,用来存放数据;
char buf_r[128] = "";
int pipefd[2];//存放文件标识符;
int ret;
ret = pipe(pipefd);//创建一个无名管道;
if(ret == -1){
perror("pipe:");
exit(EXIT_FAILURE);
}
printf("please input cammond:");
fgets(buf_w,sizeof(buf_w),stdin);
buf_w[strlen(buf_w)-1] = '\0';
pid_t pid = fork(); //创建一个子进程;
if(pid == -1){
perror("fork");
exit(EXIT_FAILURE);
}else if(pid == 0){
sleep(0.5);//延迟子进程,让主进程先进行写操作;
close(pipefd[1]);//开始读之前保证写端关闭,避免发生错误;
ssize_t rbytes = read(pipefd[0],buf_r,sizeof(buf_r));//从管道中读取数据;
if(rbytes == -1){
perror("read");
close(pipefd[0]);
exit(EXIT_FAILURE);
}
printf("command is %s,%ld in total\n",buf_r,rbytes);
close(pipefd[0]);
exit(EXIT_SUCCESS);
}else{
close(pipefd[0]);//同样先保证读端关闭;
ssize_t wbytes = write(pipefd[1],buf_w,strlen(buf_w));//向管道中写入数据;
if(wbytes == -1){
perror("write");
close(pipefd[1]);
exit(EXIT_FAILURE);
}
close(pipefd[1]);
printf("wbytes is %ld\n",wbytes);
wait(NULL);//等待子进程读取完成;
}
}
~
上面代码在进行写的时候,为避免错误,会先将读端关闭,读端同理。还有打印wbytes时的占位符时ld,因为在64位操作系统下,ssize_t类型本质上就是long int类型,但在32位的才做系统下,它是int 类型。
下面的运行结果:
虽然我们read函数传入参数读取的size是sizeof(buf_r)也就是128个,但只要遇到结束符就会停止,所以一共读了是11个是没有问题的。
此外,还需要注意的是当我们不进行写操作时,也就是说当管道为空时,读操作就会堵塞。
二、有名管道
有名管道可以在任意两个进程中进行通信,但是没有打开写进程时,会堵塞读进程。相反没有打开读进程时,写进程也会阻塞。
有名管道的创建时通过mkfifo函数:
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#define PATH_NAME "/home/linux/path_name"
int main()
{
int ret = access(PATH_NAME,F_OK);//判断文件是否存在;
if(ret == -1)
{
int res = mkfifo(PATH_NAME,0644);/*如果不存在就创建,0644实际上是8进制数,
0代表的文件的类型,后面的三位则代表着着拥有者、所属组和其他人的读、写和执行权限。
举个例子:上面的6代表的二进制数是110,那拥有者的权限就是可读可写,并没有执行权限。*/
if(res == -1){
perror("res");
exit(EXIT_FAILURE);
}
}
int fd = open(PATH_NAME,O_WRONLY);
if(fd == -1)
{
perror("open");
exit(EXIT_FAILURE);
}
char buf[256]={"Hello pipe"};
ssize_t wbytes = write(fd,buf,sizeof(buf));//写入数据;
if(wbytes == -1)
{
perror("write");
close(fd);
exit(EXIT_FAILURE);
}
printf("wbytes = %ld\n",wbytes);
close(fd);
return 0;
}
read部分:
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#define PATH_NAME "/home/linux/path_name"
int main()
{
int fd = open(PATH_NAME,O_RDONLY);
if(fd == -1)
{
perror("read");
exit(EXIT_FAILURE);
}
char buf[256]={0};
ssize_t rbytes = read(fd,buf,sizeof(buf));//将数据读到buf中;
if(rbytes == -1)
{
perror("read");
exit(EXIT_FAILURE);
}
printf("buf = %s\n",buf);
close(fd);
return 0;
}
通过write函数想管道中写入数据,然后再read的函数的进程中将数据读出来。运行结果如下:
先运行write部分:
发现它处于堵塞状态,因为我们的read部分还没有打开,暂时还无法进行写操作,那再运行read部分:
可以看到,代码成功运行,并且read部分已经将数据读出来了,是因为write在read打开的时候便将数据传了进去,read也成功接收,再看write部分:
发现也已经显示写入的字符数。
再次之后还有关闭的情况:
-
写端关闭:当写端(即写入管道的一端)关闭后,如果读端(即从管道读取数据的一端)尝试继续读取,它将不会阻塞,而是会立即返回0,表示到达了文件末尾(EOF)。这是因为管道中没有更多的数据可读,写端已经关闭。
-
读端关闭:当读端关闭后,写端如果继续写入,通常会收到一个
SIGPIPE
信号,导致进程默认终止,除非进程已经处理了这个信号。在这种情况下,写入操作不会阻塞,而是会因为管道没有读端而失败。 -
阻塞和非阻塞模式:如果管道的一端以非阻塞模式打开,那么当另一端关闭时,操作不会阻塞,而是会立即返回,并且
errno
会被设置为EAGAIN
或EPIPE
。
这些东西一定要注意。