进程通信的前提:让不同的进程看到同一份文件
匿名管道只能具有血缘关系的进程,毫不相关的进程通信得要命名管道
管道文件不需要刷盘,基于内存级文件
命名管道通过路径+文件名确定打开同一个文件,在匿名管道中利用父子进程。
创建命名管道进程
管道大小为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<sys/types.h>
#include<sys/stat.h>
#include <fcntl.h>
#include<stdio.h>
#include<iostream>
#include<unistd.h>
#include<string>
#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;
}