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

目录

  • 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.xdnf.cn/news/1555841.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

深入解析DPU:AI时代的芯片分工与超级网卡

引言 在当今AI时代&#xff0c;计算需求的爆炸性增长使得传统数据中心的架构面临着巨大的挑战。数据处理任务越来越复杂&#xff0c;尤其是在大型AI模型如GPT和BERT的应用中&#xff0c;如何提高数据中心的计算和传输效率成为关键问题。传统的CPU&#xff08;中央处理器&#…

希捷8T硬盘exfat变0字节的恢复方法

最近流行的3.5寸大容量台式硬盘移动盒子是一种性价比较高的组合&#xff0c;为了方便如涉及到跨平台&#xff08;win和mac&#xff09;&#xff0c;大多数此类组合选择了exfat文件系统。下边这个案例就是我们经常遇到的exfat变0字节。 故障存储: ST8000HKVS002 8T/exfat 文件…

第十三章 Redis短信登录实战(基于Redis)

目录 一、概述 1.1. Session复制 1.2. 使用Redis 二、基于Redis实现共享Session登录 2.1. 实现思路 2.2. 功能实现的主要代码 2.2.1. 用户业务接口 2.2.2. 用户业务接口实现类 2.2.3. 用户控制层 2.2.4. 登录拦截器 2.2.5. 拦截器配置类 2.3. 优化登录拦截器 完…

Git基本操作与分支

一、操作入门 先看大屏幕&#xff1a;先背过 再来操作 初始化 刚入门的小朋友可能出现这种问题&#xff1a; 原因是&#xff1a;需要自己创建一个记事本文件 add的作用是添加指定文件到暂存区。 commit是提交暂存区到仓库区&#xff0c;此处的仓库是本地仓库&#xff0c;本…

选择最佳HR系统_6款产品评测与推荐

本文盘点了ZohoPeople、SAPSuccessFactors等六款主流HRMS&#xff0c;各系统各具特色&#xff0c;如ZohoPeople的全球化云管理、SAP的高定制化、Workday的实时数据分析等&#xff0c;适合不同规模企业需求&#xff0c;建议企业试用后决策。 一、Zoho People Zoho People 是一个…

如何使用ssm实现基于bootstrap的课程辅助教学网站的设计与实现+vue

TOC ssm782基于bootstrap的课程辅助教学网站的设计与实现vue 第1章 绪论 1.1研究背景与意义 在科学技术水平还比较低下的时期&#xff0c;学校通常采用人工登记的方式对相关的课程信息进行记录&#xff0c;而后对这些信息记录进行管理和控制。这种采用纸质存储信息的管理模…

第十五周周报

目录 摘要Abstract1 LSTM模型实战1.1 数据处理1.2 LSTM模型的搭建1.3 数据的预测和可视化 2 transformer&#xff08;上&#xff09;2.1 Transformer 结构2.2 Transformer 编码器 总结 摘要 本周的工作内容主要分为两个部分&#xff0c;第一部分是使用LSTM模型预测股票市场数据…

固态硬盘数据丢失?别急,这4款恢复神器帮你找回“丢失的记忆”!

数据啊&#xff0c;对咱工作和生活那可老重要了。不过呢&#xff0c;固态硬盘里的数据说不定啥时候就因为不小心误操作啦&#xff0c;或者被病毒攻击啦&#xff0c;再或者硬件出毛病就丢了&#xff0c;这可真让人上火。还好哈&#xff0c;市场上有不少专门的数据恢复软件呢&…

AI少女/HS2甜心选择2 仿逆水寒人物卡全合集打包

内含AI少女/甜心选择2 仿逆水寒角色卡全合集打包共6张 内含&#xff1a;白灵雪魅落霞飞雁君临华歌白君临华歌黑平野星罗晚香幽韵 下载地址&#xff1a; https://www.51888w.com/436.html 部分演示图&#xff1a;

【Android】Handler消息机制

文章目录 前言概述核心组件概述Android消息机制概述 Android消息机制分析ThreadLocal的工作原理ThreadLocal基础ThreadLocal实现原理 MessageQueueLooperHandler的工作原理总结 前言 本文用于记录Android的消息机制&#xff0c;主要是指Handler的运行机制。部分内容参考自《An…

comfyui服装设计,一个工作流搞定!

前言 ComfyUI&#xff1a;为你的图像创作赋能的强大工具 所有的AI设计工具&#xff0c;安装包、模型和插件&#xff0c;都已经整理好了&#xff0c;&#x1f447;获取~ 在AI技术迅猛发展的今天&#xff0c;Stable Diffusion成为了图像生成领域中的一颗明星&#xff0c;而基于…

红米Turbo 3工程固件预览 修复底层 体验原生态系统 默认开启diag端口

红米Turbo 3机型代码:peridot 国外版本:POCO F6 用于以下型号的小米机型:24069RA21C, 24069PC21G, 24069PC21I。搭载1.5K OLED屏、骁龙8s处理器、5000mAh电池+90W快充、5000万像素主摄。 通过博文了解 1💝💝💝-----此机型工程固件的资源刷写注意事项 2💝💝�…

SpringBoot实战:设计与实现明星周边电子商务平台

1系统概述 1.1 研究背景 如今互联网高速发展&#xff0c;网络遍布全球&#xff0c;通过互联网发布的消息能快而方便的传播到世界每个角落&#xff0c;并且互联网上能传播的信息也很广&#xff0c;比如文字、图片、声音、视频等。从而&#xff0c;这种种好处使得互联网成了信息传…

java发起POST方法请求第三方接口(编码处理)

文章目录 引言I 案例查询船舶轨迹配置JVM编码参数请求提供方常见问题II 工具类III 知识扩展:程序运行源代码各个阶段对编码的处理Java源码--->字节码Java字节码--->虚拟机--->操作系统操作系统-->显示设备引言 使用场景: 调用第三方平台接口 I 案例 查询船舶…

基于epoll的Reactor模型

一、代码展示 1、主函数 main.cc&#xff08;第一级别&#xff09; 先控制台获取服务器的端口号&#xff0c;绑定端口号IP地址。PackageParse作为报文解析并发送接收报文的中间类&#xff0c;Listener是服务器的监听套接字&#xff0c;HandlerConnection是连接套接字&#xff…

案例-博客页面简单实现

文章目录 本文内容只涉及前端1. 内容要求2. 画面展示初始化面演示视频 3. 注意事项4. 代码区js文件夹下的jquery.min.js内容登录代码列表页面创作页面 本文内容只涉及前端 1. 内容要求 登录页面实现博客列表页面实现博客创作页面实现 链接: 开源在线 Markdown 编辑器文本框可…

【黑马点评】 使用RabbitMQ实现消息队列——2.使用RabbitMQ监听秒杀下单

2 使用RabbitMQ实现消息队列 2.1 修改\hm-dianping\pom.xmlpom.xml文件 添加RabbitMQ的环境 <!-- RabbitMQ--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </depe…

国外电商系统开发-运维系统资产属性-命令执行功能

当前开发中&#xff0c;还不支持点击拓扑图标打开资产的功能&#xff0c;后期有时间补全对应的开发。 该功能如同Xshell、SecureCRT、Putty一样&#xff0c;可以批量的发送系统命令&#xff0c;让Linux服务器执行。 默认情况下&#xff0c;系统已经选择全部主机&#xff0c;如果…

约数个数约数之和

好久没发文章了.......不过粉丝还是一个没少...... 今天来看两道超级恶心的数论题目&#xff01; No.1 约数个数 No.2 约数之和 先来看第一道&#xff1a;约数个数 题目描述 给定 n 个正整数 ai​,请你输出这些数的乘积的约数个数,答案对 10^97 取模 输入格式 第一行包含…

CUDA、Pytorch、Pycharm的安装与配置

文章目录 一、CUDA安装1.检查英伟达驱动支持的最高CUDA版本 二、Pytorch的安装与环境配置1.选择是下载CPU版本还是GPU版本2.上Pytorch官网找到安装命令3.运行指令(1)CPU版本(2)GPU版本 4.验证5.安装其他所需模块(1)安装Matplotlib(2)安装 pillow&#xff08;可能anaconda已经给…