🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:Linux
🌹往期回顾🌹:【Linux笔记】进程间通信——匿名管道||进程池
🔖流水不争,争的是滔滔不
- 一、命名管道简介
- 二、命名管道的一些特性
- 命名管道的创建
- 匿名管道与命名管道的区别
- 命名管道的打开规则
- 基于命名管道进程间通信的demo代码
一、命名管道简介
命名管道是一种特殊的文件类型,它在文件系统中有一个名字,就像普通文件一样,但它的作用不是存储数据,而是用于进程间通信。与匿名管道不同,命名管道可以在不相关的进程之间进行通信,并且可以跨越不同的主机(在支持网络命名管道的系统中)。
特点
- 半双工或全双工:命名管道可以配置为半双工或全双工模式。在半双工模式下,数据可以在两个方向上传输,但不能同时进行;在全双工模式下,数据可以同时在两个方向上传输,这使得通信更加灵活,能满足不同应用场景的需求。
- 面向字节流:命名管道以字节流的形式传输数据,这意味着发送方写入管道的数据会以连续的字节序列被接收方读取,没有固定的消息边界。接收方需要自己根据应用层的协议来解析数据。
- 同步或异步操作:对命名管道的读写操作可以是同步的,也可以是异步的。同步操作会阻塞进程,直到操作完成;异步操作则允许进程在操作进行的同时继续执行其他任务,提高了程序的并发性能。
工作原理
当一个进程打开命名管道进行写入时,操作系统会为该管道分配一个缓冲区。进程将数据写入缓冲区,然后操作系统负责将数据传递给连接到该管道的读取进程。如果管道缓冲区已满,写入进程可能会被阻塞,直到有空间可用。
读取进程从管道中读取数据时,会从缓冲区中获取数据。如果缓冲区中没有数据,读取进程可能会阻塞,直到有数据可供读取。
优缺点
- 优点:使用命名管道进行进程间通信相对简单,不需要复杂的网络编程知识。它提供了一种可靠的通信机制,保证数据的顺序性和完整性。此外,命名管道可以在不同的操作系统平台上使用,具有较好的跨平台性。
- 缺点:命名管道的性能可能不如其他一些高性能的通信机制,如共享内存。在处理大量数据时,可能会存在一定的性能瓶颈。另外,命名管道的通信是基于字节流的,需要应用程序自己处理数据的解析和格式转换,增加了编程的复杂性。
二、命名管道的一些特性
命名管道的创建
命名管道可以从命令行上创建,命令行方法是使用下面这个命令
mkfifo filename
命名管道也可以从程序里创建,相关函数
int mkfifo(const char *filename,mode_t mode);
创建命名管道:
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主逻辑的实现
#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;};
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类 里面构造命名管道与析构命名管道包含了创建管道和删除管道。
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()方法是服务端进行读操作。
客户端写
#include "comm.hpp"
int main()
{FillOper wf(PATH,FILENAME);wf.OpenForWrite();wf.Write();wf.Close();return 0;
}
服务端读
#include "comm.hpp"
int main()
{NamedFifo fifo(PATH,FILENAME);FillOper rd(PATH,FILENAME);rd.OpenForRead();rd.Read();rd.Close();return 0;
}