接上篇,我们讲到了匿名管道的具有血缘关系的了解。
现在,我们继续:
回顾:
回顾之前说到的:进程间通信的本质:让不同进程看到同一份资源。
二、管道通信(以匿名管道为例)
1. 资源本质:
- 管道是内存文件(无需刷盘),内核维护“文件缓冲区”作为通信载体。
2. 内核层面的文件打开逻辑:
两个不同进程打开同一个管道文件时,内核中仅维护1份文件资源( struct file ),但每个进程的 files_struct (文件描述符表)会通过不同的描述符(如读端、写端)指向这份资源。
3. 通信特点:
- 只能单向通信(读端、写端分离)。
- 适用于有血缘关系的进程(如父子进程,可通过继承文件描述符共享管道);无血缘关系的进程需用命名管道(通过“路径+文件名”的唯一性标识共享资源)。
三、核心逻辑
管道通信的本质是:不同进程通过共享内核中的同一份“内存文件资源”,借助其缓冲区完成数据传递。
命名管道:
无血缘关系的进程需用命名管道(通过“路径+文件名”的唯一性标识共享资源)。
创建命名管道
命令行中创建管道: mkfifo filename(文件名字) ps:这个文件名字没有在该路径下出现过的 程序中创建管道: int mkfifo(const char *filename,mode_t mode);
int main(int argc, char *argv[]) { mkfifo("xx", 0644); return 0; }
匿名管道与命名管道的区别:

匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open
它们唯一的区别在它们创建与打开的方式不同:
命名管道的打开操作:
如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功 如果当前打开操作是为写而打开FIFO时O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
用命名管道实现文件拷贝:
实现的思路跟匿名管道差不多。
通过创建一个可见于文件系统的命名管道,作为两个独立进程(读进程、写进程)的通信媒介,写进程读取源文件数据并写入FIFO,读进程从FIFO读取数据并写入目标文件,最终完成拷贝
#include <iostream> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> int main() { // 1. 打开目标文件,用于写入 int outfd = open("abc.aa", O_WRONLY | O_CREAT | O_TRUNC, 0664); if (outfd == -1) { perror("open target file (abc.aa)"); return 1; } // 2. 打开 FIFO "mk" 用于读取 int infd = open("mk", O_RDONLY); if (infd == -1) { perror("open FIFO (mk) for reading"); close(outfd); return 1; } char buffer[1024]; int n; std::cout << "Reader: Waiting to read from FIFO..." << std::endl; while ((n = read(infd, buffer, sizeof(buffer))) > 0) { if (write(outfd, buffer, n) != n) { perror("write to target file"); break; } } if (n < 0) { perror("read from FIFO"); } std::cout << "Reader: Finished reading from FIFO." << std::endl; close(infd); close(outfd); return 0; }#include <iostream> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> int main() { // 1. 打开源文件,比如 "abc" int infd = open("abc", O_RDONLY); if (infd == -1) { perror("open source file (abc)"); return 1; } // 2. 打开 FIFO "mk" 用于写入 int outfd = open("mk", O_WRONLY); if (outfd == -1) { perror("open FIFO (mk) for writing"); close(infd); return 1; } char buffer[1024]; int n; std::cout << "Writer: Sending data to FIFO..." << std::endl; while ((n = read(infd, buffer, sizeof(buffer))) > 0) { if (write(outfd, buffer, n) != n) { perror("write to FIFO"); break; } } if (n < 0) { perror("read from source"); } close(infd); close(outfd); std::cout << "Writer: Finished sending data." << std::endl; return 0; }
用命名管道实现server与client进行通信
log.hpp部分
再次之前,我们先来实现一个简便版本的日志打印功能的代码,这个后面我们也是可以直接拿来使用的!
我们对于时间的获取:用time/localtime
从上面可以看出来,time里边有个结构体tm,里面就存有其中的年月日时分秒的信息。我们就可以调用它来获取到打印日志时的时间了!
回顾snprintf/vnsprintf等打印函数接口:
#include <stdio.h> int snprintf(char *str, size_t size, const char *format, ...);snprintf 是 C 标准库中的安全格式化字符串函数,核心作用是将格式化的数据写入指定字符数组,并严格限制写入长度,避免缓冲区溢出(Buffer Overflow),这是它与不安全的 sprintf 的关键区别。
#include <stdio.h> #include <stdarg.h> int vsnprintf(char *str, size_t size, const char *format, va_list ap);vsnprintf 是 C 标准库中的可变参数格式化字符串函数,核心作用是接收一个 va_list 类型的可变参数列表,将其格式化后写入指定字符数组,并通过长度限制防止缓冲区溢出,是 snprintf 的“可变参数列表版本”,主要用于自定义可变参数函数。
ps(使用这个函数接口时,需要与va_list封装可变参数,再通过vsnprintf将用户传入的格式化内容写入)
#pragma once
#include <iostream>
#include <string>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<time.h>
#include<stdio.h>
#include<cstdarg>
#define LogFile "log.txt"
//使用枚举的方法进行管理
//根据枚举的特性,按照顺序进行递增
enum
{
Screen = 1,
OneFile,
ClassFile
};
enum
{
Info = 0,
Debug,
Warning,
Error,
Fatal
};
class Log
{
public:
Log()
{
//初始方法默认为屏幕打印
printmethod = Screen;
path = "./log/";
}
void Enable(int method)
{
printmethod = method;
}
//日志等级
std::string levelToString(int level)
{
switch (level)
{
case Info:
return "Info";
case Debug:
return "Debug";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "None";
}
}
//选择打印形式:屏幕?文件?
void PrintLog(int level,const std::string&logtxt)
{
switch (printmethod)
{
case Screen:
PrintScreen(logtxt);
break;
case OneFile:
PrintOneFile(LogFile,logtxt);
break;
case ClassFile:
PrintfClassFile(level,logtxt);
break;
default:
break;
}
}
//打印到屏幕
void PrintScreen(const std::string&logtxt)
{
std::cout<<logtxt<<std::endl;
}
//打印到一个文件中
void PrintOneFile(const std::string& filename,const std::string&logtxt)
{
std::string _filename=path+filename;
int fd=open(_filename.c_str(),O_WRONLY|O_CREAT|O_TRUNC,0664);
if(fd<0)
{
perror("open");
return;
}
char buffer[1024];
write(fd,logtxt.c_str(),logtxt.size());
close(fd);
}
//按照严重程度去分别写入到文件中
void PrintfClassFile(int level,const std::string&logtxt)
{
std::string filename=LogFile;
filename+=".";
filename+=std::to_string(level);
PrintOneFile(filename,logtxt);
}
//运算符重载,仿函数,能够让我们的日志信息像函数一样使用
void operator()(int level,const char*format,...)
{
//时间获取与格式化
time_t tm=time(nullptr);
struct tm*ctime=localtime(&tm);
char leftbuffer[1024];
snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d-%d-%d-%d]",levelToString(level).c_str(),ctime->tm_year+1900,
ctime->tm_mon+1,ctime->tm_mday,ctime->tm_hour,ctime->tm_sec);
//处理可变参数
va_list s;
va_start(s,format);
char rightBuffer[1024];
vsnprintf(rightBuffer,sizeof(rightBuffer),format,s);
va_end(s);
//格式:默认部分+自定义部分
char logtxt[11024*2];
snprintf(logtxt,sizeof(logtxt),"%s %s\n",leftbuffer,rightBuffer);
PrintScreen(logtxt);
}
~Log()
{
}
private:
//打印方法
int printmethod;
//路径
std::string path;
};
server.hpp部分
进行读数据。
#include<iostream> #include "log.hpp" #include"comm.hpp" #include<string.h> #define SIZE 1024 Log lg; int main() { Init it; lg.Enable(Screen); lg(Info,"Init done"); //打开管道 int fd=open(MYFIFO,O_RDONLY); printf("fd:%d",fd); if(fd==-1) { lg(Fatal,"error string:%s,error code:%d",strerror(errno),errno); exit(FIFO_OPEN_ERR); } lg(Info,"open done..."); //开始读取数据 while(true) { char buffer[SIZE]; int n=read(fd,buffer,sizeof(buffer)); if(n<0) { lg(Fatal,"strerr:%s,error code:%d",strerror(errno),errno); break; } if(n==0) { lg(Debug,"client quit,me too,strerr:%s,error code:%d",strerror(errno),errno); break; } else { buffer[n]=0; std::cout<<"client say#: "<<buffer<<std::endl; } } close(fd); return 0; }
client.cpp部分
进行写数据
#include<iostream> #include<string> #include"log.hpp" #include"comm.hpp" #include<cstring> Log lg; int main() { int fd=open(MYFIFO,O_WRONLY); if(fd<0) { lg(Fatal,"open fail,strerr:%s,error code:%d",strerror(errno),errno); exit(FIFO_OPEN_ERR); } lg(Info,"client open done"); //开始通信 std::string line; while(true) { std::cout<<"Please Enter# "; getline(std::cin,line); int s=write(fd,line.c_str(),line.size()); if(s<0) { lg(Fatal,"strerr:%s,error code:%d",strerror(errno),errno); return 1; } } close(fd); return 0; }
comm.hpp部分
就是创建命名管道部分
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define MYFIFO "./myfifo"
#define MODE 0664
enum
{
FIFO_CREATE_ERR = 1,
FIFO_UNLINK_ERR,
FIFO_OPEN_ERR
};
class Init
{
public:
//建立命名管道
Init()
{
int fd = mkfifo(MYFIFO, MODE);
if (fd == -1)
{
perror("mkfifo");
exit(FIFO_CREATE_ERR);
}
}
//删除命名管道
~Init()
{
int fd = unlink(MYFIFO);
if (fd == -1)
{
perror("unnlink");
exit(FIFO_UNLINK_ERR);
}
}
};
最终效果:

想了想,本篇就到此结束吧,我们下篇再见!希望大家一起进步!!
最后,到了本次鸡汤环节:
成为更好的人,新的约定
Linux命名管道通信详解









被折叠的 条评论
为什么被折叠?



