🔥个人主页🔥:孤寂大仙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;
}
