【Linux笔记】进程间通信——命名管道

🔥个人主页🔥:孤寂大仙V

🌈收录专栏🌈:Linux

🌹往期回顾🌹:【Linux笔记】进程间通信------匿名管道||进程池

🔖流水不争,争的是滔滔不


一、命名管道简介

命名管道是一种特殊的文件类型,它在文件系统中有一个名字,就像普通文件一样,但它的作用不是存储数据,而是用于进程间通信。与匿名管道不同,命名管道可以在不相关的进程之间进行通信,并且可以跨越不同的主机(在支持网络命名管道的系统中)。

特点

  • 半双工或全双工:命名管道可以配置为半双工或全双工模式。在半双工模式下,数据可以在两个方向上传输,但不能同时进行;在全双工模式下,数据可以同时在两个方向上传输,这使得通信更加灵活,能满足不同应用场景的需求。
  • 面向字节流:命名管道以字节流的形式传输数据,这意味着发送方写入管道的数据会以连续的字节序列被接收方读取,没有固定的消息边界。接收方需要自己根据应用层的协议来解析数据。
  • 同步或异步操作:对命名管道的读写操作可以是同步的,也可以是异步的。同步操作会阻塞进程,直到操作完成;异步操作则允许进程在操作进行的同时继续执行其他任务,提高了程序的并发性能。

工作原理

当一个进程打开命名管道进行写入时,操作系统会为该管道分配一个缓冲区。进程将数据写入缓冲区,然后操作系统负责将数据传递给连接到该管道的读取进程。如果管道缓冲区已满,写入进程可能会被阻塞,直到有空间可用。

读取进程从管道中读取数据时,会从缓冲区中获取数据。如果缓冲区中没有数据,读取进程可能会阻塞,直到有数据可供读取。

优缺点

  • 优点:使用命名管道进行进程间通信相对简单,不需要复杂的网络编程知识。它提供了一种可靠的通信机制,保证数据的顺序性和完整性。此外,命名管道可以在不同的操作系统平台上使用,具有较好的跨平台性。
  • 缺点:命名管道的性能可能不如其他一些高性能的通信机制,如共享内存。在处理大量数据时,可能会存在一定的性能瓶颈。另外,命名管道的通信是基于字节流的,需要应用程序自己处理数据的解析和格式转换,增加了编程的复杂性。

二、命名管道的一些特性

命名管道的创建

命名管道可以从命令行上创建,命令行方法是使用下面这个命令

bash 复制代码
mkfifo filename

命名管道也可以从程序里创建,相关函数

cpp 复制代码
int mkfifo(const char *filename,mode_t mode);

创建命名管道:

c 复制代码
int main(int argc, char *argv[])
{
	mkfifo("p2", 0644);
	return 0;
}

匿名管道与命名管道的区别

匿名管道由pipe函数创建并打开。命名管道由mkfifo函数创建,打开用open。FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

命名管道的打开规则

如果当前打开操作是为读而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO

O_NONBLOCK enable:立刻返回成功

如果当前打开操作是为写而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO

O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

基于命名管道进程间通信的demo代码

通过命名管道,客户端进行写操作,服务端进行读操作

comm.hpp主逻辑的实现

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

#define PATH "."
#define FILENAME "fifo"

class NamedFifo
{
public:
    NamedFifo(const string& path,const string& name)
    :_path(path)
    ,_name(name)
    {
        _fifoname=_path+"/"+_name;
        //创建管道
        umask(0);
        int n=mkfifo(_fifoname.c_str(),0666);
        if(n<0)
        {
            cout<<"管道创建失败"<<endl;
        }
        else
        {
            cout<<"管道创建成功"<<endl;
        }
        
    }
    ~NamedFifo()
    {
        int n=unlink(_fifoname.c_str());
        if(n<0)
        {
            cout<<"管道删除失败"<<endl;
        }
        else
        {
            cout<<"管道删除成功"<<endl;
        }
    }
private:
    string _path;
    string _name;
    string _fifoname;
};

class FillOper
{
public:
    FillOper(const string& path,const string& name)
    :_path(path)
    ,_name(name)
    ,_fd(-1)
    {
        _fifoname=_path+"/"+_name;
    }
    void OpenForRead()//打开管道文件进行读操作
    {
        _fd=open(_fifoname.c_str(),O_RDONLY);
        if(_fd<0)
        {
            cout<<"管道文件打开失败"<<endl;
        }
        else
        {
            cout<<"管道文件打开成功"<<endl;
        }
    }
    void OpenForWrite()
    {
        _fd=open(_fifoname.c_str(),O_WRONLY);
        if(_fd<0)
        {
            cout<<"管道文件打开失败"<<endl;
        }
        else
        {
            cout<<"管道文件打开成功"<<endl;
        }
    }
    void Write()//客户端写
    {
        string message;
        int cnt=1;
        pid_t id=getpid();
        while(true)
        {
            cout<<"请输入信息"<<endl;
            getline(cin,message);
            message+=(",message number:"+to_string(cnt++)+",["+to_string(id)+"]");
            write(_fd,message.c_str(),message.length());
        }
    }
    void Read()//服务端读
    {
        while(true)
        {
            char buffer[1024];
            int number=read(_fd,buffer,sizeof(buffer)-1);
            if(number>0)
            {
                buffer[number]=0;
                cout<<"client say:"<<buffer<<endl;
            }
            else if(number==0)
            {
                cout<<"客户端进程退出,服务端进程也退出"<<endl;
                break;
            }
            else
            {
                cout<<"读取错误"<<endl;
                break;
            }
        }
       
    }
    void Close()
    {
        if(_fd>0)
        {
            close(_fd);
        }
    }
    ~FillOper(){}
private:
    string _path;
    string _name;
    string _fifoname;
    int _fd;

};

cpp 复制代码
class NamedFifo
{
public:
    NamedFifo(const string& path,const string& name)
    :_path(path)
    ,_name(name)
    {
        _fifoname=_path+"/"+_name;
        //创建管道
        umask(0);
        int n=mkfifo(_fifoname.c_str(),0666);
        if(n<0)
        {
            cout<<"管道创建失败"<<endl;
        }
        else
        {
            cout<<"管道创建成功"<<endl;
        }
        
    }
    ~NamedFifo()
    {
        int n=unlink(_fifoname.c_str());
        if(n<0)
        {
            cout<<"管道删除失败"<<endl;
        }
        else
        {
            cout<<"管道删除成功"<<endl;
        }
    }
private:
    string _path;
    string _name;
    string _fifoname;
};

以上NamedFifo类 里面构造命名管道与析构命名管道包含了创建管道和删除管道。


cpp 复制代码
class FillOper
{
public:
    FillOper(const string& path,const string& name)
    :_path(path)
    ,_name(name)
    ,_fd(-1)
    {
        _fifoname=_path+"/"+_name;
    }
    void OpenForRead()//打开管道文件进行读操作
    {
        _fd=open(_fifoname.c_str(),O_RDONLY);
        if(_fd<0)
        {
            cout<<"管道文件打开失败"<<endl;
        }
        else
        {
            cout<<"管道文件打开成功"<<endl;
        }
    }
    void OpenForWrite()
    {
        _fd=open(_fifoname.c_str(),O_WRONLY);
        if(_fd<0)
        {
            cout<<"管道文件打开失败"<<endl;
        }
        else
        {
            cout<<"管道文件打开成功"<<endl;
        }
    }
    void Write()//客户端写
    {
        string message;
        int cnt=1;
        pid_t id=getpid();
        while(true)
        {
            cout<<"请输入信息"<<endl;
            getline(cin,message);
            message+=(",message number:"+to_string(cnt++)+",["+to_string(id)+"]");
            write(_fd,message.c_str(),message.length());
        }
    }
    void Read()//服务端读
    {
        while(true)
        {
            char buffer[1024];
            int number=read(_fd,buffer,sizeof(buffer)-1);
            if(number>0)
            {
                buffer[number]=0;
                cout<<"client say:"<<buffer<<endl;
            }
            else if(number==0)
            {
                cout<<"客户端进程退出,服务端进程也退出"<<endl;
                break;
            }
            else
            {
                cout<<"读取错误"<<endl;
                break;
            }
        }
       
    }
    void Close()
    {
        if(_fd>0)
        {
            close(_fd);
        }
    }
    ~FillOper(){}
private:
    string _path;
    string _name;
    string _fifoname;
    int _fd;

};

以上FillOper类里面,OpenForRead()方法是打开管道文件进行读操作,OpenForWrite()方法是打开文件进行写操作,Write()方法是客户端进行写操作,Read()方法是服务端进行读操作。


客户端写

cpp 复制代码
#include "comm.hpp"
int main()
{
    FillOper wf(PATH,FILENAME);
    wf.OpenForWrite();
    wf.Write();
    wf.Close();
    return 0;
}

服务端读

cpp 复制代码
#include "comm.hpp"
int main()
{
    NamedFifo fifo(PATH,FILENAME);
    FillOper rd(PATH,FILENAME);
    rd.OpenForRead();
    rd.Read();
    rd.Close();
    return 0;
}
相关推荐
chlk1231 天前
Linux文件权限完全图解:读懂 ls -l 和 chmod 755 背后的秘密
linux·操作系统
舒一笑1 天前
Ubuntu系统安装CodeX出现问题
linux·后端
改一下配置文件1 天前
Ubuntu24.04安装NVIDIA驱动完整指南(含Secure Boot解决方案)
linux
BingoGo2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
深紫色的三北六号2 天前
Linux 服务器磁盘扩容与目录迁移:rsync + bind mount 实现服务无感迁移(无需修改配置)
linux·扩容·服务迁移
SudosuBash2 天前
[CS:APP 3e] 关于对 第 12 章 读/写者的一点思考和题解 (作业 12.19,12.20,12.21)
linux·并发·操作系统(os)
哈基咪怎么可能是AI2 天前
为什么我就想要「线性历史 + Signed Commits」GitHub 却把我当猴耍 🤬🎙️
linux·github
BingoGo3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端