波奇学Linux:进程通信之命名管道

进程通信的前提:让不同的进程看到同一份文件

匿名管道只能具有血缘关系的进程,毫不相关的进程通信得要命名管道

管道文件不需要刷盘,基于内存级文件

命名管道通过路径+文件名确定打开同一个文件,在匿名管道中利用父子进程。

创建命名管道进程

管道大小为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;
}