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