1.1 有名管道概述
有名管道,也被称为FIFO(First In First Out),是一种在文件系统中创建的特殊文件。它允许不相关的进程通过文件系统进行通信,而不需要这些进程之间存在亲缘关系或共享内存。
命名管道(FIFO)和管道(pipe)基本相同,但也有一些显著的不同, 其特点是:
1、半双工,数据在同一时刻只能在一个方向上流动。
2、写入FIFO中的数据遵循先入先出的规则。
3、FIFO所传送的数据是无格式的,这要求FIFO的读出方与写入方必须事先约定好数据的格 式,如多少字节算一个消息等。
4、FIFO在文件系统中作为一个特殊的文件而存在并且在文件系统中可见,所以有名管道可以实现不相关进程间通信,但FIFO中的内容却存放在内存中。
5、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
6、从FIFO读数据是一次性操作,数据一旦被读,它就从FIFO中被抛弃,释放空间以便写更多的数据。
7、当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
8、FIFO有名字,不相关的进程可以通过打开命名管道进行通信。
那么再具体说明一下和管道的异同
共同特点
- 半双工通信:数据在同一时刻只能在一个方向上流动,即只能单向传输数据。
- 先进先出(FIFO)原则:写入的数据按照先入先出的规则进行读取,确保数据的有序性。
- 无格式数据传输:传输的数据是无格式的字节流,读写双方需要事先约定数据格式。
- 基于内存的缓冲区:数据在内存中进行传输和缓冲,提高了数据传输效率。
- 一次性读取操作:从管道或FIFO中读取数据是一次性操作,数据一旦被读取就会被抛弃,为新数据腾出空间。
不同之处
- 可见性与持久性:命名管道(FIFO)在文件系统中可见,并作为特殊文件存在,即使创建进程退出后仍然保留,以便后续使用。而管道(pipe)则不可见,且在使用它的进程退出后自动消失。
- 通信范围:命名管道(FIFO)允许不相关的进程通过打开管道文件进行通信,而管道(pipe)通常用于具有亲缘关系的进程之间的通信,如父子进程。
- 缓冲区大小:管道(pipe)的缓冲区大小在不同系统中可能有所不同,而命名管道(FIFO)作为文件系统中的特殊文件,其缓冲区大小可能受到文件系统的限制。
- 有名与无名:命名管道(FIFO)有具体的名称,可以通过名称来引用和访问;而管道(pipe)通常是匿名的,没有具体的名称标识。
1.2 有名管道的创建
方法1:用过shell命令mkfifo创建有名管道
mkfifo 文件名
ls -l
命令用于显示文件和目录的详细信息。
方法2:使用函数mkfifo
#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()
{
//通过mkfifo函数创建有名管道
if(mkfifo("fifo_file", 0664) == -1)
{
//printf("errno = %d\n", errno);
//如果管道文件已经存在,不需要报错退出,直接使用即可,所以需要在错误输出之前把
//因为文件存在的错误排除
if(errno != EEXIST)
{
perror("fail to mkfifo");
// exit(1);
}
}
return 0;
}
执行结果
1.3 有名管道的基本读写操作
由于有名管道在本地创建了一个管道文件,所以系统调用的IO函数基本都可以对有名管道进行操作, 但是不能使用lseek修改管道文件的偏移量
注意:有名管道创建的本地的文件只是起到标识作用,真正有名管道实现进程间通信还是在内核空间开辟内存,所以本地产生的文件只是一个标识,没有其他作用,对本地管道文件的操作实质就是对内核空间的操作
案例
#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;
}
运行结果
1.4 有名管道实现进程间通信
由于有名管道在本地创建了一个管道文件,所以不相关的进程间也可以实现通信
1.4.1 send
#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;
}
这段代码实现了一个简单的双向通信机制,其中"myfifo1"管道用于发送数据,"myfifo2"管道用于接收数据。通过这种方式,两个不相关的进程可以互相发送和接收数据,而不需要直接引用对方。
1.4.2 recv
#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;
}
我创建了两个有名管道"myfifo1"和"myfifo2",并分别以只读和只写的方式打开它们。在无限循环中,代码首先从"myfifo1"中读取数据,并将其打印出来。然后,它从标准输入读取一行数据,并将其写入到"myfifo2"中。通过这种方式,两个进程可以互相发送和接收数据。
运行结果