Linux:进程间通信->命名管道

1. 命名管道

概念

是一种进程间通信(IPC)机制,能允许没有关联的两个进程进行数据交换。

由于匿名管道只能在有亲缘关系的父子进程间通信所以具有局限性,所以就要通过命名管道来对两个没有关系的进程进行通信。

命名管道是通过路径和文件名来使两个进程找到同一份文件资源。

管道的五种特性
  1. 只能用来进行具有血缘关系的进程间通信(常用于父与子)
  2. 管道文件自带同步机制 快的等待慢的。同一时间只允许一个进程进行写入或读取操作,并且保证同一个公共资源同一时刻只能被一个进程使用,两个进程不能同时操作(互斥)
  3. 管道是面向字节流的 其大小最大为64kb,65536字节
  4. 管道是单向通信的(特殊的半双工通信)
  5. 管道文件的生命周期是随进程的(所有拥有其读写端fd的都close关闭)进程终止时,所有未关闭的fd都会被内核自动关闭。
四种通信情况
  1. 写慢,读快---读端就要阻塞(等待写端写入)。
  2. 写快,读慢---满了的时候,写就要阻塞等待
  3. 写关闭,读继续---read就会读到返回值为0,表示文件结尾
  4. 读关闭,写继续---写端再写入也没有任何意义了
    操作系统OS不做无意义的事情,OS会(发送异常信号)杀掉写端进程
命名管道创建与删除

可以通过

bash 复制代码
mkfifo 管道名称

来创建一个命名管道,如下图所示

通过下面两种方法来删除

bash 复制代码
rm -rf 命名管道名称
unlink 命名管道名称

在C/C++程序中也可以创建命名管道

所需头文件 与函数原型

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

pathname:指定要创建管道的名称(以路径的方式给出的话将命名管道创建在pathname路径下,以文件名方式给出的话,将命名管道文件默认创建在当前路径下)

mode:设置管道的权限(会被默认掩码umask影响)

返回值:成功返回0,失败返回-1 并设置errno指示错误原因

删除命名管道

所需头文件 与原型

cpp 复制代码
#include<unistd.h>

int unlink(const char *pathname);

参数:pathname 要删除文件的路径

返回值: 函数执行成功,它会返回0;如果失败,则返回-1,并设置errno以指示错误原因

2. 命名管道对文件进行备份

写进程

很简单,负责写入管道文件的进程 mkfifo创建一个管道后,open只读打开我们要进行备份的文件,open只写打开管道文件。

将要备份文件里的内容read读取到字符数组中,再将这个字符数组里的内容write写到管道文件。

最后close关闭打开的文件描述符,unlink删除管道文件(读进程没开始,写进程尝试open打开管道就会被阻塞,直到读进程打开读端)

cpp 复制代码
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>

int main()
{
    umask(0);
    //将源文件内容写入管道
    mkfifo("tp",0666);
    
    int fdout=open("abc",O_RDONLY);
    if(fdout<0) std::cerr<<"open error"<<std::endl;
    
    int fdtp=open("tp",O_WRONLY);
    if(fdtp<0) std::cerr<<"open error"<<std::endl;

    char buffer[2024];
    int n=read(fdout,buffer,sizeof buffer-1);
    if(n>0)
    {
        buffer[n]=0;
        write(fdtp,buffer,n);
    }

    close(fdout);
    close(fdtp);
    unlink("tp");
    return 0;
}
读进程

负责备份的进程,打开要备份到的文件,与管道文件,通过一个字符数组read读取管道文件里的内容,并且将其write写到要备份的文件里

最后close关闭打开的文件描述符,unlink

cpp 复制代码
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>

int main()
{
    //负责备份管道内数据
    int fdin=open("abc.bak",O_CREAT|O_RDWR|O_TRUNC,0666);
    if(fdin<0)
    {
        std::cerr<<"open error"<<std::endl;
    }
    int fdtp=open("tp",O_RDONLY);
    if(fdtp<0)
    {
        std::cerr<<"open error"<<std::endl;
    }

    char buffer[2024];

    int n=read(fdtp,buffer,2024);
    // std::cout<<buffer<<std::endl;
    if(n>0)
    {
        buffer[n]=0;
        
        write(fdin,buffer,n);
    }
    close(fdin);
    close(fdtp);
    unlink("tp");

    return 0;
}

3. 两个进程通信

分别称两个进程为客户端client和服务端server。

server

mkfifo一个管道以只读方式打开这个管道,创建一个char数组通过while循环一直读取客户端写入了什么并输出。最后关闭文件描述符并删除文件

cpp 复制代码
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
// using namespace std;
#define FIFO_FILE "fifo"
int  main()
{
    umask(0);//将umask设置为0
    int n=mkfifo(FIFO_FILE,0666);

    if(n!=0)
    {
        std::cerr<<"mkfifo error"<<std::endl;
        return 1;
    }
    int fd=open(FIFO_FILE,O_RDONLY);
    if(fd<0)
    {
        std::cerr<<"open error"<<std::endl;
        return 2;
    }
    char buffer[2024];
    while(true)
    {

        int number=read(fd,buffer,sizeof(buffer)-1);
        if(number >0)
        {
            buffer[number]=0;
            std::cout<<"client: "<<buffer<<std::endl;
        }
        else if(number==0)//读取完毕
        {
            std::cout<<"client quit! "<<std::endl;
            break;
        }
        else//读取失败
        {
            std::cerr<<"read error"<<std::endl;
        }
    }
    close(fd);
    unlink(FIFO_FILE);
    if(n==0)
    {
        std::cout<<"remove fifo success"<<std::endl;//成功
    }
    else
    {
        std::cout<<"remove fifo failed"<<std::endl;//失败
    }
    
    return 0;
}
client

以写的方式打开管道文件,通过while循环写入数据到管道文件。最后关闭文件描述符

cpp 复制代码
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
// using namespace std;
#define FIFO_FILE "fifo"
int  main()
{
    int fd=open(FIFO_FILE,O_WRONLY);
    if(fd<0)
    {
        std::cerr<<"open fifo error"<<std::endl;
        return 2;
    }
    std::string message;//消息
    int cnt=1;
    pid_t id=getpid();
    while(true)
    {
        std::cout<<"请输入:";

        std::getline(std::cin,message);
        message+=" message num: "+std::to_string(cnt++)+" ["+std::to_string(id)+"]";
        // std::cout<<message<<std::endl;
        int n=write(fd,message.c_str(),message.size());
        
    }
    close(fd);
    return 0;
}

演示结果如下

变为面向对象的形式

使用一个类创建命名管道

cpp 复制代码
#pragma once
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#define FIFO_FILE "fifo"
class NameFifo
{
public:
     NameFifo(const std::string& path,const std::string& name)
     :_path(path),_name(name)
     { 
        _fifoname=_path+"/"+_name;
        umask(0);
        int n=mkfifo(_fifoname.c_str(),0666);//将管道按照指定路径构建出来
        if(n<0)
        {
            std::cerr<<"mkfifo error"<<std::endl;//失败
        }
        else
        {
            std::cout<<"mkfifo success"<<std::endl;//成功
        }

     }  
     ~NameFifo()
     {
        int n=unlink(_fifoname.c_str());//删除管道文件
        if(n==0)
        {
            std::cout<<"remove fifo success"<<std::endl;
        }
        else
        {
            std::cout<<"remove fifo failed"<<std::endl;
        }
     }
private:
     std::string _path;
     std::string _name;
     std::string _fifoname;
};

成员

_path:路径

_name 命名管道名称

_fifoname _path+_name 更方便记录

使用一个类来实现 1. 打开读端 2. 打开写端 3. 写 4. 读 5. 关闭文件描述符 等操作

cpp 复制代码
class FileOper
{
public:
    FileOper(const std::string& path,const std::string& name)
    :_path(path),_name(name)
    {
        _fifoname=_path+"/"+_name;
    }
    void OpenForRead()
    {
        _fd=open(_fifoname.c_str(),O_RDONLY);
        if(_fd<0)
        {
            std::cerr<<"open fifo error"<<std::endl;
            return ;
        }
    }
    void OpenForWrite()
    {
        _fd=open(_fifoname.c_str(),O_WRONLY);
        if(_fd<0)
        {
            std::cerr<<"open fido success"<<std::endl;
            return ;
        }
    }
    void Write()
    {
        int cnt=1;
        int id=getpid();
        while(true)
        {
            std::string message;
            std::getline(std::cin,message);
            message+=" message num: "+std::to_string(cnt++)+" ["+std::to_string(id)+"]";
            
            write(_fd,message.c_str(),message.size());
        }
    }
    void Read()
    {
        char buffter[2024];
        while(true)
        {
            int n=read(_fd,buffter,sizeof buffter-1);
            if(n>0)
            std::cout<<"client: "<<buffter<<std::endl;
            else if(n==0)
            {
                std::cout<<"client quit!"<<std::endl;
                break;
            }
            else
            {
                std::cerr<<"error"<<std::endl;
                break;
            }
        }
    }
    void Close()
    {
        if(_fd>0)
        close(_fd);
    }
    ~FileOper()
    {}
private:
    std::string _path;
    std::string _name;
    std::string _fifoname;
    int _fd;
};

此时server代码变为了

cpp 复制代码
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include"comm.hpp"
#include<fcntl.h>
// using namespace std;
// #define FIFO_FILE "fifo"
int  main()
{
    NameFifo fifo(".","fifo");

    FileOper readerfile(".","fifo");

    readerfile.OpenForRead();
    readerfile.Read();

    readerfile.Close();

    return 0;
}

client变为了

cpp 复制代码
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include"comm.hpp"
#include<fcntl.h>
// using namespace std;

int  main()
{
    FileOper writefile(".","fifo");

    writefile.OpenForWrite();
    writefile.Write();
    writefile.Close();
    return 0;
}

这篇就到这里啦(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

相关推荐
AlfredZhao13 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户97183563346619 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪21 小时前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux
小宇宙Zz2 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工2 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信