目录
? ?有名管道(Named Pipe)是一种在文件系统中存在的特殊文件,它允许不相关的进程之间进行通信。与无名管道不同,有名管道通过一个路径名在文件系统中存在,并且可以由多个进程同时访问。
有名管道概述 命名管道(FIFO)和管道(pipe)基本相同,但也有一些显著的不同, 其特点是:
1、半双工,数据在同一时刻只能在一个方向上流动。
2、写入FIFO中的数据遵循先入先出的规则。
3、FIFO所传送的数据是无格式的,这要求FIFO的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
4、FIFO在文件系统中作为一个特殊的文件而存在并且在文件系统中可见,所以有名管道可以实现不相关进程间通信,但FIFO中的内容却存放在内存中。
5、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
6、从FIFO读数据是一次性操作,数据一旦被读,它就从FIFO中被抛弃,释放空间以便写更 多的数据。
7、当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
8、FIFO有名字,不相关的进程可以通过打开命名管道进行通信。
查看系统man手册查看无名管道原型:
man 3 mkfifo
方法1:用过shell命令mkfifo创建有名管道 :mkfifo[空格]文件名。
1 方法2:使用函数mkfifo 2
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道,产生一个本地文件系统可见的文件pathname。
参数: pathname:有名管道创建后生成的文件,可以带路径。
?mode:管道文件的权限,一般通过八进制数设置即可,例如0664。
返回值:成功:0 ?失败:‐1
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
//通过mkfifo函数创建有名管道
if(mkfifo("fifo_file", 0664) == -1)
{
//printf("errno = %d\n", errno);
//如果管道文件已经存在,不需要报错退出,直接使用即可,所以需要在错误输出之前把
//因为文件存在的错误排除
if(errno != EEXIST)//EEXIST文件已存在错误(17)。
{
perror("fail to mkfifo");
exit(1);
}
}
return 0;
}
? ? ? 由于有名管道在本地创建了一个管道文件,所以系统调用的IO函数基本都可以对有名管道 进行操作, 但是不能使用lseek修改管道文件的偏移量。
? ? ? ?注意:有名管道创建的本地的文件只是起到标识作用,真正有名管道实现进程间通信还是在 内核空间开辟内存,所以本地产生的文件只是一个标识,没有其他作用,对本地管道文件的 操作实质就是对内核空间的操作。
? ? ? ? 操作 FIFO 文件时的特点 系统调用的 I/O 函数都可以作用于 FIFO,如 open、close、read、write 等。 打开 FIFO 时,非阻塞标志(O_NONBLOCK)产生下列影响:
特点一: 不指定 O_NONBLOCK(即 open 没有位或 O_NONBLOCK)
1、open 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO
2、open 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define FIFONAME "fifo_file"
int main(int argc, char const *argv[])
{
//通过mkfifo函数创建有名管道
if(mkfifo(FIFONAME, 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//对有名管道进行操作
//管道后写入的数据会保存在之前写入数据的后面,不会替换
//如果管道中没有数据了,读操作会阻塞
//通过open函数打开管道文件并得到文件描述符
int fd;
fd = open(FIFONAME, O_RDWR);
if(fd == -1)
{
perror("fail to open");
exit(1);
}
//通过write函数向管道中写入数据
if(write(fd, "hello world", strlen("hello world")) == -1)
{
perror("fail to write");
exit(1);
}
write(fd, "nihao beijing", strlen("nihao beijing"));
//通过read函数读取管道中的数据
char buf[32] = "";
if(read(fd, buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = [%s]\n", buf);
if(read(fd, buf, sizeof(buf)) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = [%s]\n", buf);
//使用close函数关闭文件描述符
close(fd);
return 0;
}
由于有名管道在本地创建了一个管道文件,所以不相关的进程间也可以实现通信
为了实现两个进程都可以收发数据,所以需要创建两个有名管道!
send.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//如果没有创建有名管道,则创建有名管道
//为了实现两个进程都可以收发数据,所以需要创建两个有名管道
if(mkfifo("myfifo1", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
if(mkfifo("myfifo2", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//打开两个有名管道并得到文件描述符
int fd_w, fd_r;
if((fd_w = open("myfifo1", O_WRONLY)) == -1)
{
perror("fail to open");
exit(1);
}
if((fd_r = open("myfifo2", O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
char buf[128] = "";
ssize_t bytes;
while(1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
//send进程负责将数据写入myfifo1,接着从myfifo2中读取数据
if((bytes = write(fd_w, buf, sizeof(buf))) == -1)
{
perror("fail to write");
exit(1);
}
if((bytes = read(fd_r, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
printf("from recv: %s\n", buf);
}
return 0;
}
recv.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
if(mkfifo("myfifo1", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
if(mkfifo("myfifo2", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
int fd_w, fd_r;
if((fd_r = open("myfifo1", O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
if((fd_w = open("myfifo2", O_WRONLY)) == -1)
{
perror("fail to open");
exit(1);
}
char buf[128] = "";
ssize_t bytes;
while(1)
{
if((bytes = read(fd_r, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
printf("from send: %s\n", buf);
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
write(fd_w, buf, sizeof(buf));
}
return 0;
}
read.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
if(mkfifo("myfifo", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//读写端都存在,只读不写
//如果原本管道中有数据,则正常读取
//如果管道中没有数据,则read函数会阻塞等待
int fd;
if((fd = open("myfifo", O_RDWR)) == -1)
{
perror("fail to open");
exit(1);
}
write(fd, "hello world", 11);
char buf[128] = "";
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
return 0;
}
write.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
if(mkfifo("myfifo", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//读写端都存在,只写不读
//当有名管道的缓冲区写满后,write函数会发生阻塞
//默认有名管道的缓冲区为64K字节
int fd;
if((fd = open("myfifo", O_RDWR)) == -1)
{
perror("fail to open");
exit(1);
}
int num = 0;
while(1)
{
write(fd, "", 1024);
num++;
printf("num = %d\n", num);
}
return 0;
}
onlyRead.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
if(mkfifo("myfifo", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//在一个进程中,只有读端,没有写端
//会在open函数的位置阻塞
printf("***********************\n");
int fd;
if((fd = open("myfifo", O_RDONLY)) == -1)
{
perror("fail to open");
exit(1);
}
printf("------------------------\n");
char buf[128] = "";
ssize_t bytes;
while(1)
{
if((bytes = read(fd, buf, sizeof(buf))) == -1)
{
perror("fail to read");
exit(1);
}
printf("bytes = %ld\n", bytes);
printf("buf = %s\n", buf);
}
return 0;
}
onlyWrite.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
if(mkfifo("myfifo", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//在一个进程中,只有写端,没有读端
//会在open函数的位置阻塞
printf("*****************************\n");
int fd;
if((fd = open("myfifo", O_WRONLY)) == -1)
{
perror("fail to open");
exit(1);
}
printf("-----------------------------\n");
while(1)
{
write(fd, "hello world", 11);
printf("666\n");
sleep(1);
}
return 0;
}
将上面onlyRead.c和onlyWrite.c两个代码一起运行,保证有名管道读写端都存在
规律:
只要保证有名管道的读写端都存在,不管是几个进程,都不会再open这阻塞了。
如果一个进程只读,一个进程只写,都运行后,如果关闭写端,读端read会返回0。
如果一个进程只读,一个进程只写,都运行后,如果关闭读端,写端会立即产生 SIGPIPE信号,默认的处理方式是退出进程。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd;
if(mkfifo("myfifo", 0664) == -1)
{
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
#if 0
//如果open标志位设置为非阻塞,并且以只读的方式打开管道文件
//open函数和read函数都不会阻塞
fd = open("myfifo", O_RDONLY | O_NONBLOCK);
if(fd < 0)
{
perror("open fifo");
exit(1);
}
while(1)
{
char recv[100];
bzero(recv, sizeof(recv));
read(fd, recv, sizeof(recv));
printf("read from my_fifo buf=[%s]\n",recv);
sleep(1);
}
#endif
#if 1
//如果open标志位设置为非阻塞,并且以只写的方式打开管道文件
//open函数会直接报错
//如果open设置为可读可写,那么跟阻塞是一样的效果
char send[100] = "Hello I love you";
fd = open("myfifo", O_WRONLY | O_NONBLOCK);
//fd = open("myfifo", O_RDWR | O_NONBLOCK);
if(fd < 0)
{
perror("open fifo");
exit(1);
}
write(fd, send, strlen(send));
char recv[100];
read(fd, recv, sizeof(recv));
printf("read from my_fifo buf=[%s]\n",recv);
#endif
return 0;
}
一:不指定 O_NONBLOCK(即 open 没有位或 O_NONBLOCK)
1、open 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO。
2、open 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO。
二:指定 O_NONBLOCK(即 open 位或 O_NONBLOCK)
1、先以只读方式打开:如果没有进程已经为写而打开一个 FIFO, 只读 open 成功,并且 open 不阻塞。
2、先以只写方式打开:如果没有进程已经为读而打开一个 FIFO,只写 open 将出错返回-1。
3、read、write 读写命名管道中读数据时不阻塞。
4、通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会(收到 SIGPIPE 信号)退出。