当前位置: 首页 > news >正文

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);}}
};

最终效果:

想了想,本篇就到此结束吧,我们下篇再见!希望大家一起进步!!

最后,到了本次鸡汤环节:

成为更好的人,新的约定

http://www.dtcms.com/a/598463.html

相关文章:

  • 烟台品牌网站建设求职
  • byd APP逆向(AES白盒分析)
  • QueryWrapper 与 LambdaQueryWrapper 深度解析:优劣对比、选择指南及用户表实战案例
  • 【完整源码+数据集】车牌数据集,yolov8车牌检测数据集 7811 张,汽车车牌识别数据集,智慧交通汽车车牌识别系统实战教程
  • 婚庆网站策划便捷网站建设价格
  • 视频+教程 | 三位一体:MOI 数据源 + MO 向量存储 + Dify 应用层,构建企业级 RAG
  • 侨联网站建设网站开发实训报告总结2021
  • 怎么做会员积分网站房地产开发资质
  • 智能服务管理的临界点:当AI成为ITSM的“神经中枢”
  • 太原制作网站的公司百度云服务器做asp网站
  • 学途-人工智能机器学习课程
  • 什么是网站内页wordpress如何上传到服务器
  • 网站 宣传方案淘宝店铺 发布网站建设
  • 论find -group和-gid的区别
  • Spring Cloud中分布式事务的监控和日志使用小窍门
  • LeetCode(python)——560.和为k的子数组
  • cae毕业设计代做网站淮北论坛招聘最新消息兼职
  • 今天我们学习zabbix网络设备监控的配置
  • NRBO-XGBoost+SHAP分析+新数据预测!机器学习可解释分析不在发愁!提供9种混沌映射方法(tent、chebyshev、singer等)
  • 两学一做教育网站家政服务app软件开发
  • 网站建设必学课程企业文化包括哪些内容
  • Poco: 一个功能丰富、易于使用的跨平台C++开发框架(FTP上传下载、断点续传等)
  • 网站建设学什么专业网络营销的发展前景
  • 做公众号首图网站wordpress禁止访问模版页面
  • 如何更新Dev-C++到最新版本?
  • 传统文化信息|文化管理|基于java的传统文化信息管理系统设计与实现(源码+数据库+文档)
  • 【题解】[GESP样题 七级] 迷宫统计
  • 丰台广州网站建设tomcat 建网站
  • 基于 GEE 利用 WorldPop 数据集批量导出 100 米分辨率人口影像数据与时序分析
  • 《实施意见》推动新场景应用:乡村政务场景如何借AI破局