进程通信的前提:让不同的进程看到同一份文件
匿名管道只能具有血缘关系的进程,毫不相关的进程通信得要命名管道
管道文件不需要刷盘,基于内存级文件
命名管道通过路径+文件名确定打开同一个文件,在匿名管道中利用父子进程。
创建命名管道进程
管道大小为0,意味在磁盘占据的大小为0,所以它数据不刷盘
把数据重定向到有名管道,进程阻塞直到,数据被提取
当myfifo的数据被提取此时进程才结束阻塞
在上面的例子中cat 和echo相当于两个bash进程,myfifo在两个进程中实现通信
如果是两个手写的可执行程序该如何实现通信,红色的线代表不存在
结构上我们依然使用和匿名管道一样的结构,只是两个进程和file的关系要手动建立
手写程序实现进程通信
利用mkfifo创建管道文件
实现思路
1.创建管道文件
2.通过相同的文件名,client端写入信息到文件,server端从文件中读取信息。(上面的信息client端和server的红黑线画反了)
client代码
#include "comm.hpp" int main() { //创建管道文件 int result=mkfifo(Pathname,Mode); if(result) { perror("Create mkfifo error\n"); // Create mkfifo error: xxx } printf("successfully create file\n"); // 进程连接管道 int fd=open(Pathname,O_WRONLY); if (fd==-1) { perror("open error\n"); } // 写入文件 string line; while (1) { printf("please enter your message: \n"); getline(cin,line); printf("\n"); printf("client send message: %s\n",line.c_str()); ssize_t num=write(fd,line.c_str(),line.size());//count =size是因为char型是一个字节 if(num==-1) { perror("Write error\n"); break; } if(num==0) { break; } } // 删除管道 当写端删除时,读端读到0,自动接结束 close(fd); printf("Delete file\n"); int n=unlink(Pathname); if(n!=0) { perror("Delete file error\n"); } return 0; }
server代码
#include"comm.hpp" int main() { int fd=open(Pathname,O_RDONLY); if (fd==-1) { perror("open error\n"); } //读取 char* buff[Size]; while(1) { ssize_t num=read(fd,buff,Size); //会等待write输入 if(num==0) { printf("client quit now, me too\n"); break; } else if(num==-1) { //num == -1 printf("read error\n"); break; } else { buff[num]=0; printf("receive message %s from client\n",buff); } } return 0; }
头文件comm.hpp代码
//引用共同的同文件可以使得管道名共享 #pragma once #include#include #include #include #include #include #include #define Mode 0664 #define Pathname "./myfifo" #define Size 1024 using namespace std;
代码细节点
在文件写入的printf要\n刷新,在server读取前就强制刷新出来。
unlink的作用是删除管道文件,不然再次运行时会触发mkfifo创建失败,理由是file exists
在client close(fd)关闭写端,读端读取到0,自动退出
getline的作用是读取空格
写端直接输入换行符刷新缓冲区,读端读取到0
Pathname表示文件路径+文件名 ./myfifo 和myfifo 等架 ../myfifo则在上一级目录中生成
read函数会处于阻塞状态,直到write函数写入文件或直接刷新缓冲区
代码改进点
1.可以利用类对象析构函数的自动调用,来保证unlink函数调用。
2.当触发错误,此时应退出进程,所以可以自定义错误码并用exit函数退出。
3.管道不应该由client创建,而是server服务端创建,用户不该创建管道,而是由软件的服务端创建好等待用户输入。
修改后部分的代码
comm.hpp代码
enum{ OPEN_FILE_ERROR=1, //由1开始向后排 CREATE_FILE_ERROR, DELETE_FILE_ERROR, READ_FILE_ERROR, WRITE_FILE_ERROR }; class Pipe { public: Pipe() { //创建管道文件 int result=mkfifo(Pathname,Mode); if(result) { perror("Create mkfifo error\n"); // Create mkfifo error: xxx exit(CREATE_FILE_ERROR); } printf("successfully create file\n"); } ~Pipe() { printf("Delete file\n"); int n=unlink(Pathname); if(n!=0) { perror("Delete file error\n"); exit(DELETE_FILE_ERROR); } } };
server.cc代码
#include"comm.hpp" int main() { Pipe p(); int fd=open(Pathname,O_RDONLY); if (fd==-1) { perror("open error\n"); exit(OPEN_FILE_ERROR); } //读取 char* buff[Size]; while(1) { ssize_t num=read(fd,buff,Size); //会等待write输入 if(num==0) { printf("client quit now, me too\n"); break; } else if(num==-1) { //num == -1 printf("read error\n"); exit(READ_FILE_ERROR); break; } else { buff[num]=0; printf("receive message %s from client\n",buff); } } return 0; }