Linux编程:5、进程通信-命名管道
一、命名管道的定义与用途
- 定义:命名管道是一种特殊的文件,不同于匿名管道,它有文件名,可在文件系统中看到。
- 用途:解决匿名管道只能在有血缘关系进程(如父子进程或者有共同祖先的进程)间通信的限制,允许无血缘关系的进程间通信。
二、命名管道的创建方式
(一)命令行方式
- 命令:
mkfifo 管道名
,例如mkfifo myFifo
。 - 查看属性:使用
ll -l
命令,管道文件类型标记为p
,末尾可能有|
符号。
- 直接读命名管道: 若没有其它进程将数据写入命名管道的操作就会阻塞
- 直接写命名管道: 若没有其它进程从命名管道读入数据的操作也会阻塞
- 一个进程读、一个进程写:
单纯的读或写命名管道都会被阻塞,必需在写操作的时候存在另一个进程在读命名管道才不会被阻塞
(二)编程方式
- 函数原型:
int mkfifo(const char *pathname, mode_t mode);
。 - 参数说明:
pathname
:命名管道的路径。mode
:管道文件的权限(如0666
表示读写权限)。
- 结果:创建一个命名管道文件
- 返回值:成功返回 0,失败返回 - 1。
- 代码演示:
// test1.cpp的代码#include <cstdlib> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h>#define FIFO_NAME "/root/test/my_fifo" // 这里记得改成你自己的路径int main() {if(access(FIFO_NAME, F_OK) == -1){// 不存在int ret = mkfifo(FIFO_NAME, 0660);if(ret != 0) {fprintf(stderr, "创建命名管道(%s)失败.\n", FIFO_NAME);exit(-1);}}printf("进程(%d)正在已读的方式打开管道(%s)...\n", getpid(), FIFO_NAME);int fd = open(FIFO_NAME, O_RDONLY);if (fd == -1) {fprintf(stderr, "进程(%d)打开管道 %s 失败\n", getpid(), FIFO_NAME);} else {printf("进程(%d)打开管道 %s 成功\n", getpid(), FIFO_NAME);} }
// test2.cpp的代码#include <cstdlib> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h>#define FIFO_NAME "/root/test/my_fifo" // 这里记得改成你自己的路径int main() {if(access(FIFO_NAME, F_OK) == -1) {int ret = mkfifo(FIFO_NAME, 0660);if(ret != 0) {fprintf(stderr, "创建命名管道(%s)失败", FIFO_NAME);exit(-1);}}printf("进程(%d)正在以写的方式打开命名管道(%s)...\n", getpid(), FIFO_NAME);int fd = open(FIFO_NAME, O_WRONLY);if (fd == -1) {fprintf(stderr, "进程(%d)打开管道 %s 失败\n", getpid(), FIFO_NAME);}else {printf("进程(%d)打开管道 %s 成功\n", getpid(), FIFO_NAME);}return 0; }
-
运行结果:
只运行了读操作
再运行写操作
三、命名管道的打开模式
(一)阻塞模式(默认)
- 读模式打开:若没有写进程打开管道,读操作会阻塞。
- 写模式打开:若没有读进程打开管道,写操作会阻塞。
(二)非阻塞模式(O_NONBLOCK)
- 组合使用:
- 读模式 + O_NONBLOCK:打开时不阻塞,直接执行后续代码。
- 写模式 + O_NONBLOCK:打开时不阻塞,直接执行后续代码。
四、命名管道的读写特性
(一)读操作特性
- 若无数据可读,读操作阻塞,直到有数据写入。
- 若管道写端被关闭,读操作返回 0。
(二)写操作特性
- 若管道已满,写操作阻塞,直到有空间可用。
- 若管道读端被关闭,写操作会导致错误。
- 数据长度限制:
- 若写入数据长度≤PIPE_BUF(通常 4KB),操作原子化,不会与其他写操作交错。
- 若写入数据长度 > PIPE_BUF,可能部分写入或失败。
(三)管道大小
- 命名管道的大小为 PIPE_BUF,由系统决定,一般为 4KB。
五、读写数据示例
1、从命名管道中读数据
// test1.cpp的代码
#include <fcntl.h>
#include <limits.h>
#include <linux/limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>#define FIFO_NAME "/root/test/my_fifo" // 这里记得改成你自己的路径int main()
{if (access(FIFO_NAME, F_OK) == -1) {// 不存在int ret = mkfifo(FIFO_NAME, 0660);if (ret != 0) {fprintf(stderr, "创建命名管道(%s)失败.\n", FIFO_NAME);exit(-1);}}// 以读的方式打开这个命名管道printf("进程(%d)正在以读的方式打开管道(%s)...\n", getpid(), FIFO_NAME);int fd = open(FIFO_NAME, O_RDONLY);if (fd == -1) {fprintf(stderr, "进程(%d)打开管道 %s 失败\n", getpid(), FIFO_NAME);}else {printf("进程(%d)打开管道 %s 成功\n", getpid(), FIFO_NAME);}// 读操作的初始buff全都是 'a'std::string buff(PIPE_BUF, 'a');int ret = 0;int count = 0;do {ret = read(fd, buff.data(), buff.size());if (ret < 0) {fprintf(stderr, "读管道失败");exit(1);}count += ret;} while (ret > 0);close(fd);printf("进程(%d)读到 %d 字节\n", getpid(), count);// 读完后,buff全都是 'b'printf("buff = \n%s\n", buff.c_str());return 0;
}
2、从命名管道中写数据
// test2.cpp的代码#include <fcntl.h>
#include <limits.h>
#include <linux/limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>#define FIFO_NAME "/root/test/my_fifo" // 这里记得改成你自己的路径int main()
{if (access(FIFO_NAME, F_OK) == -1) {// 不存在int ret = mkfifo(FIFO_NAME, 0660);if (ret != 0) {fprintf(stderr, "创建命名管道(%s)失败.\n", FIFO_NAME);exit(-1);}}// 以写的方式打开这个命名管道printf("进程(%d)正在以写的方式打开管道(%s)...\n", getpid(), FIFO_NAME);int fd = open(FIFO_NAME, O_WRONLY);if (fd == -1) {fprintf(stderr, "进程(%d)打开管道 %s 失败\n", getpid(), FIFO_NAME);}else {printf("进程(%d)打开管道 %s 成功\n", getpid(), FIFO_NAME);}std::string buff(PIPE_BUF, 'b');int total = 10 * 1024 * 1024;int count = 0;while (count < total) {int ret = write(fd, buff.c_str(), buff.size());if (ret < 0) {fprintf(stderr, "管道写入错误");exit(1);}count += total;}close(fd);return 0;
}
3、运行结果
六、注意事项
- 打开同步问题:默认阻塞模式下,单独打开读或写管道会阻塞,需同时有读和写进程配合。
- 原子性写入:多个进程同时写入时,确保每次写入数据长度≤PIPE_BUF,避免数据交错。
- 权限问题:创建管道时需注意权限设置,确保其他进程有读写权限。
- 管道生命周期:命名管道文件在文件系统中持续存在,需手动删除或通过程序处理。