目录
1、无名管道
1.无名管道的特点
2.pipe函数创建管道
3.图例
2、命名管道(FIFO)
1.命名管道的特点
2.mkfifo 函数-创建命名管道
3.示例
1.循环读取数据
2.循环写入数据
1、无名管道
管道通常指的就是无名管道,
1.无名管道的特点
- 管道是半双工的,只支持数据的单向传输,有固定的读端和写端。
- 无名管道用于有亲缘关系的进程间通信
- 无名管道可以看作一种特殊的文件,对它的读写我们可以直接使用普通的read、write等函数进行操作,但是注意无名管道并不是普通的文件,它不存在任何的文件系统中,只存在于内存中
- 管道里的数据被读走了就会从管道中消失
2.pipe函数创建管道
pipe
函数是用于创建管道的系统调用,通常用于在父子进程之间进行单向通信
函数原型
int pipe(int pipefd[2]);
参数:
pipefd
: 一个包含两个文件描述符的整数数组,pipefd[0]
用于读取数据,pipefd[1]
用于写入数据。返回值:
- 如果成功,返回 0。
- 如果失败,返回 -1,并设置
errno
指示错误类型。pipe函数会创建两个文件描述符,pipefd[0]是读端、pipefd[1]是写端
需要注意的是,
pipe
函数创建的管道是阻塞的,如果管道已满(写端写入速度过快),写操作将会被阻塞,直到有空间可用
3.图例
如果要使数据从父进程流向子进程就需要关闭父进程的读端和子进程的写端,反之就使数据从子进程流向父进程
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main() {
int fd[2]; // 用于存储管道的文件描述符,fd[0] 为读取端,fd[1] 为写入端
int pid; // 用于存储 fork 函数的返回值,表示当前是父进程还是子进程
char buf[128] = {0}; // 用于存储从管道中读取的数据
// 创建管道
if (pipe(fd) == -1) {
printf("pipe 创建失败\n");
perror("pipe");
exit(-1); // 如果 pipe 创建失败,退出程序,返回 -1
}
// 创建子进程
pid = fork();
if (pid < 0) {
printf("fork 创建失败\n");
perror("fork");
exit(-1); // 如果 fork 创建失败,退出程序,返回 -1
} else if (pid > 0) { // 父进程
printf("parent process\n");
close(fd[0]); // 关闭读取端,因为父进程只负责写入数据
write(fd[1], "holle wjh happy", strlen("holle wjh happy")); // 向管道写入数据
wait(NULL); // 等待子进程结束
} else { // 子进程
printf("child process\n");
close(fd[1]); // 关闭写入端,因为子进程只负责读取数据
read(fd[0], buf, 128); // 从管道读取数据到 buf 中
printf("read: %s\n", buf);
exit(0); // 子进程正常结束,返回 EXIT_SUCCESS
}
return 0;
}
2、命名管道(FIFO)
命名管道(Named Pipe)是一种特殊类型的管道,与无名管道(通过 pipe
创建的)不同,它具有文件系统中的名字,并且可以通过文件系统路径来访问。命名管道在进程间通信中提供了一种有趣而强大的方式。
1.命名管道的特点
- 命名管道是通过在文件系统中创建一个具有名字的特殊文件来实现的。这个文件的路径就是命名管道的名字
- 命名管道的使用方式与文件类似,可以使用标准的文件读写操作函数来进行进程间通信。打开命名管道后,可以使用
read
和write
等函数进行数据传输。 - 命名管道常用于独立的进程之间通信。在这种情况下,各个进程可以通过打开相同路径名的命名管道文件来进行通信,也就是说命名管道可以在无关进程之间进行通信,这里和无名管道不同
- 像无名管道一样,命名管道的读写操作也是阻塞的。如果没有数据可读,读取操作会被阻塞,直到有数据为止;如果管道已满,写入操作会被阻塞,直到有空间可用。
2.mkfifo 函数-创建命名管道
函数原型
int mkfifo(const char *pathname, mode_t mode);
mkfifo是 "make FIFO" 的缩写,用于创建一个新的命名管道
参数:
pathname
:要创建的命名管道的路径名。mode
:创建的文件权限。通常使用八进制表示,例如0666
。返回值:
- 如果成功创建命名管道,返回 0。
- 如果失败,返回 -1,并设置
errno
表示具体的错误类型。注意:
- 在成功创建命名管道后,可以使用标准的文件操作函数(如
open
、read
、write
等)来进行读写操作。- 如果已经存在同名的普通文件或目录,
mkfifo
会失败。
3.示例
1.循环读取数据
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
int main() {
char buf[30] = {0};
// 尝试创建命名管道
if (mkfifo("./file", 0600) == -1 && errno != EEXIST) {
printf("mkfifo 失败!!!\n");
perror("mkfifo");
exit(EXIT_FAILURE);
} else if (errno == EEXIST) {
perror("mkfifo");
// 如果管道已经存在,继续执行
}
// 打开命名管道进行读取
int fd = open("./file", O_RDONLY);
if (fd == -1) {
printf("open 失败!!!\n");
perror("open");
exit(EXIT_FAILURE);
} else {
// 循环读取数据
while (1) {
int read_count = read(fd, buf, 20);
if (read_count > 0) {
printf("read_count = %d, reads = %s\n", read_count, buf);
} else if (read_count == 0) {
// 如果 read 返回 0,表示已经读到文件末尾,可以采取适当的处理方式
printf("End of file reached. Exiting...\n");
break;
} else {
// 处理 read 函数返回的错误
perror("read");
break;
}
}
// 关闭文件描述符
close(fd);
}
return 0;
}
2.循环写入数据
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
// 创建一个名为 "file" 的命名管道
if (mkfifo("./file", 0600) == -1 && errno != EEXIST) {
// 如果创建失败且不是因为文件已存在,则输出错误信息并退出程序
printf("mkfifo 失败!!!\n");
perror("mkfifo");
exit(EXIT_FAILURE);
} else if (errno == EEXIST) {
// 如果管道已经存在,则输出相应的错误信息
perror("mkfifo");
}
// 打开命名管道进行写入
int fd = open("./file", O_WRONLY);
if (fd == -1) {
// 如果打开失败,则输出错误信息
perror("open");
} else {
// 在循环中每秒写入一次数据到管道中
while (1) {
sleep(1);
int write_count = write(fd, "happy bay wjh", strlen("happy bay wjh"));
printf("write_count = %d\n", write_count);
}
// 关闭文件描述符
close(fd);
}
return 0;
}