【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;
}
相关推荐
A小辣椒2 天前
TShark:Wireshark CLI 功能
linux
A小辣椒2 天前
TShark:基础知识
linux
BingoGo2 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack2 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao3 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户3074596982073 天前
PHP 扩展——从入门到理解
php
用户9718356334663 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 天前
linux 拷贝文件或目录到指定的位置
linux
鹏仔先生3 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php