【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;
}
相关推荐
大学生亨亨13 分钟前
go语言八股文(五)
开发语言·笔记·golang
安顾里41 分钟前
Linux命令-iostat
linux·运维·服务器
100编程朱老师1 小时前
面试:什么叫Linux多路复用 ?
linux·运维·服务器
miracletiger2 小时前
uv 新的包管理工具总结
linux·人工智能·python
enyp802 小时前
麒麟系统(基于Ubuntu)上使用Qt编译时遇到“type_traits文件未找到”的错误
linux·qt·ubuntu
struggle20252 小时前
LinuxAgent开源程序是一款智能运维助手,通过接入 DeepSeek API 实现对 Linux 终端的自然语言控制,帮助用户更高效地进行系统运维工作
linux·运维·服务器·人工智能·自动化·deepseek
无敌小茶3 小时前
Linux学习笔记之动静态库
linux·笔记
DXM05214 小时前
牟乃夏《ArcGIS Engine地理信息系统开发教程》学习笔记3-地图基本操作与实战案例
开发语言·笔记·学习·arcgis·c#·ae·arcgis engine
程序员JerrySUN4 小时前
驱动开发硬核特训 · Day 21(上篇) 抽象理解 Linux 子系统:内核工程师的视角
java·linux·驱动开发
雨声不在5 小时前
debian切换用户
linux·服务器·debian