【进程间通信(二)】【命名管道】

devtools/2024/10/22 12:26:20/

目录

  • 1. 命名管道
    • 1.1 现象
    • 1.2 理解
    • 1.3 编码通信
  • 2. 了解日志
    • 2.1 了解可变参数
    • 2.2 在通信中加入日志信息

进程通信(一)】【管道通信(上)】
进程通信(一)】【管道通信(下)】
这两篇文章所说的进程通信,借助的管道都是匿名管道,也就是没有名字的管道,包括我们命令行中 | 符号,这些都是匿名管道。而这篇文章则围绕命名管道进行展开叙述。

1. 命名管道

因为匿名管道没有名字,而进程通信的前提需要让双方看到同一份 “资源”,因此只有父子进程才能够以匿名管道的方式进行通信(只要父进程创建管道,子进程就会继承父进程的文件描述符表,这样子进程也同样能够看到这个管道资源),而这种只能在具有血缘关系的进程体系中进行通信

1.1 现象

所以还有一种命名管道,用于没有任何关系的进程之间的通信

在命令行中,除了 | 创建匿名管道之外,还可以 mkfifo 指令创建命名管道

mkfifo myfifo	# 创建命名管道[outlier@localhost fifo]$ mkfifo myfifo
[outlier@localhost fifo]$ ll
total 0
prw-rw-r-- 1 outlier outlier   0 Aug 23 19:54 myfifo

而当我们对该命名管道进行写入时,如果读端没有对管道做读取,那么写端是会处于阻塞状态的。

在这里插入图片描述

在这里插入图片描述

1.2 理解

  • 如果两个不同的进程打开同一个文件时,在内核中,操作系统会打开几个文件?

    首先我们能够很清楚的是,不同的进程打开文件,方式有读有写,可能不一样,并且每个进程对文件读写的位置也可能不一致,因此对于 struct file,每个进程肯定是不一样的。但是对于被打开的这个文件,其文件属性、该文件的读写方法,文件缓冲区,这些都是相同的。

    缓冲区都是一样的,当不同进程对文件读写数据时,不会导致数据的混乱吗? ---- 当你让不同的进程同时打开一个文件做读写操作,这个问题就不是操作系统所能控制的了。即便给你分配多个缓冲区,但最后你将数据刷到外设时,一样无法保证数据的有序性。因此操作系统干脆不管这个问题,由用户层自己控制。

进程通信的前提是:不同的进程能够看到同一份 “资源”,而既然不同的进程都能打开同一个文件,不就等于看到同一份 “资源" 了吗??不是说看到同一份资源就能够进行通信吗??

但是通信的场景是,一个进程把数据丢进文件缓冲区中,让另一个进程从缓冲区把数据读出去,能够做到这一步就足够了,不需要什么刷盘。所以这肯定不能是普通文件,即便不同进程打开同一个普通文件,看到共同的资源了,依旧不能进行通信,因为普通文件是会进行数据刷盘的!

因此管道文件为什么能够进行通信,就是因为它不用刷盘!它是内存级别的文件!只需要有一个缓冲区即可。所以当不断的向管道文件追加写入数据后,依旧可以发现管道文件的大小是 0,因为它根本就没刷盘,在磁盘中也就自然没有数据。

  • 如何得知不同的进程打开的是同一个文件?

    这一块与父子进程不同,因为子进程继承了父进程的数据,因此可以子进程可以看到父进程打开的那一个文件。但是如果没有血缘关系的进程,通过管道文件所在的路径 + 文件名,就可以让该文件在该路径下绝对具有唯一性,路径 + 文件名定位文件,那么就能保证不同进程打开的是同一个文件。而只要打开的是同一个文件(内存级),那么就能够实现进程间的通信

所以 有路径、有名字的管道,就称为命名管道 关于命名管道的其它原理,与匿名管道是一致的!

1.3 编码通信

命名管道通信中使用到的系统接口

// 创建命名管道的系统调用
// pathname:路径 + 文件名
// mode:初始化管道文件的权限
int mkfifo(const char *pathname, mode_t mode);
RETURN VALUEOn success mkfifo() returns 0.  In the case of an error,-1 is returned (in which case, errno is set appropriately).示例:
#define FIFO_FILE "./myfifo"
#define MODE 0664
int n = mkfifo(FIFO_FILE, MODE);
// 删除一个文件的系统调用
// path: 路径 + 文件名
int unlink(const char *path);
RETURN VALUEUpon successful completion, 0 shall be returned. Otherwise, -1 shall be returned and errno set to indicate the error. If  -1 is returned, the named file shall not be changed.示例:
#define FIFO_FILE "./myfifo"
int m = unlink(FIFO_FILE);

通信代码案例:

// comm.hpp
#pragma once#include<iostream>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string>#define FIFO_FILE "./myfifo"
#define MODE 0664enum
{FIFO_CREATE_ERR = 1,FIFO_DELETE_ERR,FIFO_OPEN_ERR
};class Init
{
public:Init(){int n = mkfifo(FIFO_FILE, MODE);if(n == -1)     // 创建管道失败返回 -1{perror("mkfifo");exit(FIFO_CREATE_ERR);}}~Init(){int m = unlink(FIFO_FILE);      // unlink 删除文件的系统调用if(m == -1){perror("unlink");exit(FIFO_DELETE_ERR);}}
};
// service.cc
#include "comm.hpp"
using namespace std;int main()
{ // 1. 创建管道Init init;// 2. 打开管道// 等待写入方打开之后,自己才会打开文件,即如果写端没打开,读的一方会阻塞在open处// 因为如果写端没打开,都没数据写入,还读什么数据int fd = open(FIFO_FILE, O_RDONLY);    if(fd < 0){perror("open");exit(FIFO_OPEN_ERR);}cout << "server open file done!\n";    // 3. 通信while(true){char buffer[1024] = {0};int sz = read(fd, buffer, sizeof(buffer));if(sz > 0){buffer[sz] = 0;cout << "[client]$ " << buffer << endl;}else if(sz == 0)    // 写端关闭,读端也关闭{cout << "client quie!\n";break;}else break;}close(fd);return 0;
}
// client.cc
#include "comm.hpp"
using namespace std;int main()
{// 1. 打开管道int fd = open(FIFO_FILE, O_WRONLY);     // 客户端写if(fd < 0){perror("open");exit(FIFO_OPEN_ERR);}cout << "client open file done!\n";string line;while(true){cout << "Please Enter# ";getline(cin, line);write(fd, line.c_str(), line.size());}close(fd);return 0;
}

2. 了解日志

在实际开发中,不管是本地软件,还是服务端,难免会存在一些问题,那么这些问题就需要被记录下来,方便开发人员后续的查找和解决。

一般的日志包含时间、等级、内容,甚至有些还会包含文件名和行号等信息。

常见的日志等级有:

  • Info:常规消息
  • Warning:报警信息
  • Error:比较严重了,可能需要立即处理
  • Fatal:致命的错误
  • Debug:调试

2.1 了解可变参数

在将日志应用到我们的代码案例之前,我们需要先了解一下可变参数的使用,方便后续使用日志。

va_list: C 库中的一个宏,底层是 char* 结构,在函数实例化参数时,可变参数具有不确定性,而 va_list 用于提取可变参数中每一个参数。

// 让 va_list 指向的可变参数部分,本质就是让 va_list 指向 &last + 1
void va_start(va_list ap, last);
// 根据类型提取可变参数 
type va_arg(va_list ap, type);	示例:
int sum(int n, ...)
{	va_list s;// 让 s 指向可变参数的起始地址,即找到 n 的地址后,指针在向后移动 n 的大小个字节偏移量即可找到。// 可变参数必须至少要有一个具体的参数,就是因为需要靠这个具体的参数找到可变参数部分。va_start(s, n);		int ret = 0;while(n--) ret += va_arg(s, int);va_end(s);      //含义:s == NULLreturn ret;
}// 调用
sum(3, 1, 2, 3);		// ret=6
sum(5, 1, 2, 3, 4, 5);	// ret=15

2.2 在通信中加入日志信息

了解需要用到的接口

time_t time(time_t *tloc);	// 获取时间戳
struct tm *localtime(const time_t *timep);		// 将时间戳格式化
struct tm {int tm_sec;         /* seconds */int tm_min;         /* minutes */int tm_hour;        /* hours */int tm_mday;        /* day of the month */int tm_mon;         /* month */int tm_year;        /* year */int tm_wday;        /* day of the week */int tm_yday;        /* day in the year */int tm_isdst;       /* daylight saving time */
};
// 需要注意的是,返回的年份是从1900往后开始计数的,因此计算真实年份时需要加上 1900
// 月份需要 + 1
·tm_mon    The number of months since January, in the range 0 to 11.
·tm_year   The number of years since 1900.
int snprintf(char *str, size_t size, const char *format, ...);
// 与 snprintf 相似,只不过将可变参数列表换成了 va_list 宏,用于定位可变参数部分
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
// log.hpp
#pragma once
#include<iostream>
#include<stdarg.h>
#include<string>
#include<time.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"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 operator()(int level, const char* format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftBuffer[SIZE];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_min, ctime->tm_sec);va_list s;va_start(s, format);    // 初始化s,让其指向可变参数部分char rightBuffer[SIZE * 2];vsnprintf(rightBuffer, sizeof(rightBuffer), format, s);char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftBuffer, rightBuffer);printLog(level, logtxt);}void printLog(int level, const std::string& logtxt){switch(printMethod){case Screen:std::cout << logtxt << "\n";break;case Onefile:printOneFile(LogFile, logtxt);break;               case Classfile:printClassFile(level, logtxt);break;}}void printOneFile(const std::string& logName, const std::string& logtxt){std::string file = path + logName;int fd = open(file.c_str(), O_CREAT|O_WRONLY|O_APPEND, 0666);if(fd < 0) return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string& logtxt){std::string file = LogFile;file += '.';file += levelToString(level);printOneFile(file, logtxt);}~Log() {}
private:int printMethod;std::string path;
};

日志输出在代码中的应用案例

Log log;
log.Enable(Classfile);  // 多文件打印
int fd = open(FIFO_FILE, O_RDONLY);    
if(fd < 0)
{log(Fatal, "server open file done, error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);
}while(true)
{char buffer[1024] = {0};int sz = read(fd, buffer, sizeof(buffer));if(sz > 0) {...}else if(sz == 0)    // 写端关闭,读端也关闭log(Info, "server quit! error string: %s, error code: %d", strerror(errno), errno); break;else log(Error, "read pipe done, error string: %s, error code: %d", strerror(errno), errno); break;
}

如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!


http://www.ppmy.cn/devtools/122137.html

相关文章

【12月IEEE出版* 镇江 】第九届清洁能源与发电技术国际学术会议(CEPGT 2024)

2024 9th International Conference on Clean Energy and Power Generation Technology (CEPGT 2024) 第九届清洁能源与发电技术国际学术会议&#xff08;CEPGT 2024&#xff09;&#xff0c;将于2024年12月27-29日在江苏镇江举办。高效清洁新能源的研究和应用是我国现阶段最主…

Polars 的 DataFrame

DataFrame 聚合 (Aggregation)属性 (Attributes)计算 (Compute)描述性 (Descriptive)导出 (Export)分组 (Groupby)选择 (Selection)操作 (Operations)杂项 (Miscellaneous)PlotStyle 聚合 (Aggregation) agg(): 对DataFrame中的列进行聚合操作。sum(): 计算列的总和。mean(): …

手机号码测吉凶接口

手机号码测吉凶接口通常指的是一种在线服务或API&#xff0c;它根据用户输入的手机号码&#xff0c;通过特定的算法或规则来判断该号码的吉凶。以下是对手机号码测吉凶接口的详细分析&#xff1a; 一、接口背景与原理 背景&#xff1a; 手机号码测吉凶在中国传统文化中具有一定…

Python项目文档生成常用工具对比

写在前面&#xff1a; 通过阅读本片文章&#xff0c;你将了解&#xff1a;主流的Python项目文档生成工具&#xff08;Sphinx&#xff0c;MkDocs&#xff0c;pydoc&#xff0c;Pdoc&#xff09;简介及对比&#xff0c;本文档不涉及相关工具的使用。 概述 近期&#xff0c;由于…

数据仓库的建设——从数据到知识的桥梁

数据仓库的建设——从数据到知识的桥梁 前言数据仓库的建设 前言 企业每天都在产生海量的数据&#xff0c;这些数据就像无数散落的珍珠&#xff0c;看似杂乱无章&#xff0c;但每一颗都蕴含着潜在的价值。而数据仓库&#xff0c;就是那根将珍珠串起来的线&#xff0c;它能够把…

安卓使用memtester进行内存压力测试

memteser简介 memtester 是一个用于测试内存可靠性的工具。 它可以对计算机的内存进行压力测试&#xff0c;以检测内存中的错误&#xff0c;例如位翻转、随机存取错误等。memtester 可以在不同的操作系统上运行&#xff0c;并且可以针对不同大小的内存进行测试。 下载源码 m…

ROS无人机机械爪使用

引言&#xff1a;使用飞控的主通道5-8作为舵机控制输出&#xff0c;需要提前设置好飞控参数&#xff0c;否则无效。本节资料文件尚未整理完毕&#xff0c;整理完毕后会在B站进行视频讲解&#xff0c;并进行开源 1、启动mavros通信&#xff0c;用于订阅遥控器的按键信息&#x…

FireRedTTS - 小红书最新开源AI语音克隆合成系统 免训练一键音频克隆 本地一键整合包下载

小红书技术团队FireRed最近推出了一款名为FireRedTTS的先进语音合成系统&#xff0c;该系统能够基于少量参考音频快速模仿任意音色和说话风格&#xff0c;实现独特的音频内容创造。 FireRedTTS 只需要给定文本和几秒钟参考音频&#xff0c;无需训练&#xff0c;就可模仿任意音色…