进程通信————命名管道
1.命名管道基础概念与创建
1.1 核心作用
用于无血缘关系的进程间通信,突破进程隔离限制,实现不同会话、不同程序进程间的数据交换。
1.2 创建方式
通过mkfifo命令创建命名管道文件:
mkfifo myfifo # 创建名为myfifo的命名管道
1.3 文件特性
- 是一种特殊符号文件,仅作为进程找到内核缓冲区的 “标识”,不存在于磁盘中(内存级文件)。
- 让两个无血缘关系的进程打开同一个文件的方法是让它们看到同路径下的同一个文件名(路径+文件名),因其具有唯一性。
- 数据不持久化到磁盘,无需刷盘,仅在内存中临时存储。
2.通信现象与流程
2.1 典型通信场景
无血缘关系的两个进程通过命名管道myfifo通信的完整流程如下:
第一步,会话 1 的写方执行echo "hello linux" > myfifo向管道写入数据,此时因会话 2 的读方未进行任何操作,写进程会因无读方而进入阻塞等待状态。
第二步,会话 1 的写方保持阻塞状态时,会话 2 的读方执行cat myfifo打开管道准备读取数据,读方打开管道后,会触发写方的阻塞解除。
第三步,写方将数据写入内核缓冲区,读方从缓冲区读取数据并在终端显示,数据从写方成功传递到读方,且读取后会自动清除。
2.2 核心现象总结
- 写操作需等待读方打开管道,否则会处于阻塞状态;读操作若缓冲区为空且没有写方,同样会阻塞。
- 数据按 “先进先出”(FIFO)规则传递,一次交互完成后缓冲区会被清空。
3.通信原理与内核机制
3.1 进程通信前提
不同进程需 “看到同一份资源”,即通过相同的路径 + 文件名打开命名管道。路径 + 文件名的唯一性确保了不同进程能够指向同一个内核缓冲区。
3.2 内核文件管理
- 多个进程打开同一命名管道时,内核仅维护1 套文件结构,这套结构包含:
- 文件属性(如 inode 信息)、共享缓冲区以及操作方法(read/write等)。
- 内核采用单实例管理的原因:避免资源冗余,简化维护工作,无需重复存储相同的属性和缓冲区。
3.3 并发与同步
- 多进程同时进行读写操作时,若不加以保护会导致数据错乱(内核不保证操作的原子性)。
- 原理可参照匿名管道,其核心是通过内核缓冲区实现 “写进程→缓冲区→读进程” 的数据流向。
4.相关接口
4.1 mkfifo函数(创建命名管道)
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
作用:创建路径为pathname的命名管道文件,供无血缘或有血缘关系的进程通信。
参数说明:
- const char *pathname:指定管道的路径及文件名(如"./myfifo")。
- mode_t mode:指定管道权限(如0666表示所有者、组用户、其他用户均有读写权限)。
- 实际权限计算:最终权限 = mode & ~umask(受当前进程umask影响)。
返回值:
- 成功:返回0;
- 失败:返回-1,并设置errno(如路径已存在、权限不足等)。
4.2 unlink函数(删除管道文件)
#include <unistd.h>
int unlink(const char *pathname);
作用:删除指定路径的文件条目,对于命名管道:
- 调用后管道从文件系统中移除,但已打开管道的进程仍可继续使用。
- 直到所有进程关闭管道,内核才会彻底释放其资源。
返回值:
- 成功:返回0;
- 失败:返回-1,并设置errno(如文件不存在、权限不足等)。
4.3 代码示例
com.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>// 定义命名管道文件路径(当前目录下的myfifo文件)
#define FIFO_FILE "./myfifo"
// 定义命名管道的创建权限(0666表示所有者、组用户、其他用户均有读写权限)
#define MODE 0666
// 定义缓冲区大小(1024字节)
#define N 1024
using namespace std;// 定义错误码枚举(提高代码可读性)
enum{FIFO_ERROR=1, // 创建命名管道失败的错误码UNLINK_ERROR, // 删除命名管道失败的错误码OPEN_ERROR, // 打开命名管道失败的错误码(预留)READ_ERROR // 读取命名管道失败的错误码(预留)
};// 命名管道初始化与清理类
class Init{
public:// 构造函数:创建命名管道Init(){int ret=mkfifo(FIFO_FILE,MODE);if(ret==-1){perror("mkfifo");exit(FIFO_ERROR);}}// 析构函数:移除命名管道~Init(){//移除命名管道int m=unlink(FIFO_FILE);if(m==-1){perror("unlink");exit(UNLINK_ERROR);}}
};
server.cpp
#include "com.hpp"
using namespace std;
int main(){// 创建Init类对象,触发构造函数自动创建命名管道// 程序退出时会自动调用析构函数删除命名管道Init init;// 打开命名管道文件,以只读方式(O_RDONLY)打开int fd=open(FIFO_FILE,O_RDONLY);if(fd<0){perror("open");exit(OPEN_ERROR);}// 进入循环,持续读取管道中的数据(实现持续通信)while(true){char buffer[N]={0};ssize_t n=read(fd,buffer,sizeof(buffer));if(n>0){// 读取成功(返回值为实际读取的字节数)buffer[n]='\0';cout<<"Client say:"<<buffer<<endl;}else if(n==0){// 读取到0表示写端关闭管道(EOF)cout<<"Client quit,me too!"<<endl;break;}else{// 读取失败perror("read");exit(READ_ERROR);}}// 通信结束后关闭管道文件描述符,释放资源close(fd);return 0;
}
client.cpp
#include "com.hpp"int main(){// 打开命名管道文件,以只写模式(O_WRONLY)打开int fd=open(FIFO_FILE,O_WRONLY);if(fd<0){perror("open");exit(OPEN_ERROR);}//发送信息string msg;// 用于存储用户输入的消息while(true){// 进入循环,持续获取用户输入并发送消息cout<<"Please send message:";getline(cin,msg);write(fd,msg.c_str(),msg.size());}//写完内容关闭文件close(fd);return 0;
}