Linux--进程间通信(2)
接上篇,我们讲到了匿名管道的具有血缘关系的了解。
现在,我们继续:
回顾:
回顾之前说到的:进程间通信的本质:让不同进程看到同一份资源。
二、管道通信(以匿名管道为例)
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 0664enum
{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);}}
};
最终效果:

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








