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

【Linux】进程间通信(三):命名管道

📝前言:

这篇文章我们来讲讲Linux 进程间通信(三)——命名管道

🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏


这里写目录标题

  • 一,命名管道介绍
    • 1. 原理介绍
    • 2. 简单创建
    • 3. 简单使用
    • 4. 管道文件特殊的读写规则
    • 5. 命名管道删除
  • 二,使用示例
    • 错误和退出宏
    • 示例1
      • 思路
      • 代码
      • 运行
    • 示例2
      • 思路
      • 代码
      • 运行

一,命名管道介绍

之前我们介绍过了匿名管道,匿名管道通常用于有血缘关系的进程。那如果两个毫无关系的进程之间要进行通信,这时候就需要用到我们的命名管道。

1. 原理介绍

首先,想要进行进程间通信,就要让两个进程看到同一份资源!这份资源就是文件!
命名管道的原理和匿名管道基本相同,都是要让两个进程看到同一份文件。那两个没有血缘关系的进程是如何做到的呢?

在这里插入图片描述

  • 当两个进程打开同一路径下的同一个文件的时候,对应的文件描述符会被写入对应进程的文件描述符表中
  • 而对应文件的struct file其实是会被拷贝一份的(尽管两个struct file描述的都是c.txt文件,但是两个进程可能会在文件中有不同的读写位置不同的struct file就会记录)
  • 但是文件的 inode 和 文件内核缓冲区(这是文件在内存中真正的主体)是不会拷贝的,因为都是同一个文件的。
  • 即:因为路径具有唯一性,两个进程利用路径的唯一性看到了同一个文件(资源)。因为文件有文件名,所以叫做命名管道。

问题是,对于普通文件来说,IO会往磁盘刷新!
所以,OS提供了一种特殊的文件:管道文件,这种文件不会往磁盘刷新。这样两个进程就可以通过读写这个文件来实现通信!

2. 简单创建

我们用mkfifo来创建管道文件,命令和库函数同名

命令:
在这里插入图片描述
库函数:
在这里插入图片描述

  • 参数:(文件路径,创建文件时的权限)
  • 返回值:成功:0,失败:-1

示例(命令行创建):

mkfifo fifo.txt

在这里插入图片描述
这个p就代表是管道文件

示例(库函数创建):

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{int ret = mkfifo("fifo2.txt", 0666);if(ret == 0)std::cout << "创建管道文件fifo2.txt成功" << std::endl;return 0;
}

在这里插入图片描述
当命名管道创建好后,和普通文件的使用放个一模一样。先打开,然后再读写操作。

3. 简单使用

命令行输入:echo "hello world" > fifo.txt
结果:
在这里插入图片描述
就像被卡柱一样,直到我们cat fifo.txt才有反应:
在这里插入图片描述
在这里插入图片描述
这是因为,管道文件有特殊的读写规则。

4. 管道文件特殊的读写规则

管道文件的读写端必须都打开了才能工作,不然一段会端塞。

以上面的例子为例,

  • 当使用 echo "hello world" > fifo.txt 往管道写数据时,必须有另一个进程在管道的读端等待读取数据,否则写操作会被阻塞

  • 同理,如果先打开读端,但是写端没有打开,读端也会阻塞等待写端打开。

5. 命名管道删除

使用unlink
在这里插入图片描述
当然,命令行也可以用rm
在这里插入图片描述
系统调用删,常用于代码中

二,使用示例

错误和退出宏

  • 当一个操作错误时,C语言会设置对应的错误码在errno,我们可以通过perror来打印对应错误码的错误信息。
  • 如果我们想在打印错误信息后立刻终止程序,可以添加exit操作

上述两个操作,我们可以用一个宏 + do...while(0)来包装一下

#define ERR_EXIT(m) \
do { \perror(m); \exit(EXIT_FAILURE); \
} while(0)
  • do...while(0):只是为了提供一个能用于代码块包装的{}
  • 后续写代码时,我们直接调用ERR_EXIT(m)m为我们传入的字符串提示信息
  • 因为宏是要写到一行的,\是为了换行
  • EXIT_FAILURE 是标准库 <stdlib.h> 中已经定义的宏(通常值为 1

示例1

思路

重建两个进程,利用命名管道进行文件的拷贝:把file1.txt 的 内容拷贝到 file2.txt

基本思路:

  • 一个进程往命名管道里面写入file1.txt的内容
  • 另一个进程从命名管道读内容,写入file2.txt

代码

WriteN 文件:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>using namespace std;#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)int main()
{// 创建命名管道int m = mkfifo("tp", 0666);if (m < 0)ERR_EXIT("mkfifo"); // 代表mkfifo这个行为发生错误// 打开原有文件int infd = open("file1.txt", O_RDONLY);if (infd < 0)ERR_EXIT("open");// 打开命名管道int outfd = open("tp", O_WRONLY);if (outfd < 0)ERR_EXIT("open");// 把原有文件的内容输出到命名管道char buffer[1024];int n;while ((n = read(infd, buffer, sizeof(buffer))) > 0) // 当还读到有数据时{write(outfd, buffer, n);}close(infd);close(outfd);return 0;
}

ReadN文件:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>using namespace std;#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)int main()
{// 打开命名管道int infd = open("tp", O_RDONLY);if(infd < 0) ERR_EXIT("open");// 打开要写入的文件int outfd = open("file2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);if (outfd < 0)ERR_EXIT("open");// 把命名管道的内容写到要拷贝到的文件char buffer[1024];int n;while((n = read(infd, buffer, sizeof(buffer))) > 0) // 当还读到有数据时{write(outfd, buffer, n);}close(infd);close(outfd);// 删除命名管道unlink("tp");return 0;
}

运行

一个终端先运行writer,另一个终端再运行reader,就完成了文件拷贝工作
在这里插入图片描述

示例2

思路

  • 利用命名管道建立一个服务段和客户端对话的窗口。
  • 客户端往命名管道里面写数据。
  • 服务段从命名管道里面读数据。

代码

comm.hpp文件:

#pragma once#include <string>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>#define PATH "."
#define FILENAME "fifo"using namespace std;#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)class NamedPipe
{
public:NamedPipe(string path, string name): _path(path), _name(name){_fifoname = _path + "/" + _name;int m = mkfifo(_fifoname.c_str(), 0666);if (m < 0)ERR_EXIT("mkfifo");cout << "mkfifo " << _fifoname << "sucess" << endl;}~NamedPipe(){int u = unlink(_fifoname.c_str());if (u < 0)ERR_EXIT("unlink");cout << "unlink " << _fifoname << "sucess" << endl;}private:string _path;string _name;string _fifoname;
};class Link // 服务端和用户端通过这个Link对象来进行对话
{
public:Link(const string& path, const string& name) // 这个构造只是为了把上面创建的命名管道的信息拿下来: _path(path), _name(name){_fifoname = _path + "/" + _name;cout << "Link " << _fifoname << " -> sucess" << endl;}void LinkForWrite(){// 当命名管道创建好了以后,就当做普通文件一样访问_fd = open(_fifoname.c_str(), O_WRONLY | O_TRUNC);if (_fd < 0)ERR_EXIT("open");cout << "Now you can write something to server " << endl;}void LinkForRead(){_fd = open(_fifoname.c_str(), O_RDONLY);if (_fd < 0)ERR_EXIT("open");cout << "Now you can read from client " << endl;}void Write(){while (true){string message;getline(cin, message);if(message == "quit"){break;}write(_fd, message.c_str(), message.size());}}void Read(){while (true){char buffer[1024];int n = read(_fd, buffer, sizeof(buffer) - 1);buffer[n] = '\0';if (n > 0){cout << buffer << endl;}else if (n == 0){cout << "cilent quit... now server quit" << endl;exit(EXIT_SUCCESS);}else{ERR_EXIT("read");}}}void Close(){close(_fd);}private:string _path;string _name;string _fifoname;int _fd;
};

client.cpp文件:

#include "comm.hpp"int main()
{Link Client(PATH, FILENAME);Client.LinkForWrite();Client.Write();Client.Close();
}

server.cpp文件

#include "comm.hpp"int main()
{// 创建命名管道// 构造时创建,进程结束时,自动析构NamedPipe fifo(PATH, FILENAME);Link Server(PATH, FILENAME);Server.LinkForRead();Server.Read();Server.Close();
}

Makefile文件:

.PHONY:all
all:client server
client:client.cppg++ -o $@ $^ -std=c++11
server:server.cppg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client server
  • 伪目标all(不对应实际文件,仅用于执行命令),依赖于clientserver
  • make 执行all的时候,就会去执行clientserver目标

运行

在这里插入图片描述

在这里插入图片描述


🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

相关文章:

  • PyTorch进阶实战指南:01自定义神经网络组件开发
  • JavaScript 性能优化:调优策略与工具使用
  • Java转Go日记(四十四):Sql构建
  • 深入解析 HTTP 中的 GET 请求与 POST 请求​
  • Android Framework学习七:Handler、Looper、Message
  • 【DCGMI专题1】---DCGMI 在 Ubuntu 22.04 上的深度安装指南与原理分析(含架构图解)
  • 谷歌宣布推出 Android 的新安全功能,以防止诈骗和盗窃
  • Opencv常见学习链接(待分类补充)
  • 企业级物理服务器选型指南 - 网络架构优化篇
  • 【小明剑魔视频Viggle AI模仿的核心算法组成】
  • 什么是Rootfs
  • Python的蚁群优化算法实现与多维函数优化实战
  • 雷军:芯片,手机,平板,SUV一起发
  • Java 06API时间类
  • Backend - Oracle SQL
  • Sql刷题日志(day9)
  • Ansible模块——管理100台Linux的最佳实践
  • Ansible模块——通过 URL 下载文件
  • HTTP/HTTPS与SOCKS5协议在隧道代理中的兼容性设计解析
  • django回忆录(Python的一些基本概念, pycharm和Anaconda的配置, 以及配合MySQL实现基础功能, 适合初学者了解)
  • 总书记回信二周年之际,上海如何将垃圾分类深度融入城市发展?
  • 陈龙带你观察上海生物多样性,纪录片《我的城市邻居》明播出
  • 优质文化资源下基层,上海各区优秀群文团队“文化走亲”
  • 左手免费午餐右手花开岭,邓飞14年公益之路的中国贡献
  • 国家统计局:中美大幅降低关税有利于双方贸易增长,也有利于世界经济复苏
  • 解放日报“解码上海AI产业链”:在开源浪潮中,集聚要素抢先机