Linux 进程间通信之命名管道

news2024/11/24 3:41:24

💓博主CSDN主页:麻辣韭菜💓

⏩专栏分类:Linux知识分享⏪

🚚代码仓库:Linux代码练习🚚

🌹关注我🫵带你学习更多Linux知识
  🔝 

目录

前言

 命名管道

创建一个命名管道 

代码实现 

日志 

 可变参数列表

 获取时间的函数locatime

 snprintf函数

 snprintf函数



​ 


前言

书接上回,进程间通信我们利用管道可以通信,但是这些进程都是有血缘关系的进程,那有没有能让两个毫不相干的进程也能通信?有的,我们用命名管道,就能实现两个没有任何关系的进程进行通信。 

 命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用 FIFO 文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件

创建一个命名管道 

 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

指令: mkfifo filename

 

echo本来是输出到显示器的,我们加入重定向后,重定到filename这个命名管道文件中,

cat输入重定向从filename当中读取数据 hello world ,我们再ll命令发现filename文件大小还是0。f

 

那如何删除命名管道啊?

指令:unlink filename 或者 rm -f filename

 

这时你会想知道,命令管道如何在程序中如何创建?代码实现我们先不急,先理解两个没有任何关系的进程,它们进程间通信的底层原理。

        从匿名管道中我们知道进程想要通信,首先就是要让两个进程看到同一份资源,匿名管道天然具有这种优势,子进程会继承父进程的所有资源。但是命名管道不是这样的,那如何让两个进程看到同一份资源? 

        那就是两个进程打开同一个文件。那如何确保两个进程打开的是同一份文件?首先从OS层面来说,我管你是进程A还是进程B又或者进程C,你们三打开同一个文件,看似打开了三次,实际上OS为了效率,它只会打开一次这个文件,既然这样,那两个进程要通信,直接打开文件名相同的且路径相同文件就行了。为什么加路径?因为路径是唯一的。所以我们就能确保两个进程打开的是同一份文件。

这时有人就会问了,那我直接在两个进程中用系统调用open这个函数打开文件不就看到同一份资源了吗?一个进行读,一个进行写不就通信了吗?为什么还要用命名管道。首先两个进程从磁盘上读取,加载到内存,又从内存中写入,拷贝到磁盘中,这个过程就很慢,凡是和磁盘打交道,那就是慢慢慢!!!而刚才我们看到filename是没有字节的,这就意味着命名管道是不需要刷盘的,它是内存级的文件速度很快。 

代码实现 

实现之前我们需要先了解一个系统调用接口 mkfifo

mkfifo 函数用于创建一个 FIFO (First-In-First-Out)特殊文件,也就是一个命名管道。以下是 mkfifo 函数的手册页翻译:

名称 mkfifo - 创建一个 FIFO 特殊文件(命名管道)

头文件

            #include <sys/types.h>

            #include <sys/stat.h>

   int mkfifo(const char *pathname, mode_t mode);

描述 mkfifo() 用指定的 pathname 创建一个 FIFO 特殊文件。mode 参数指定了 FIFO 的权限。文件的权限会受进程的掩码(umask)影响:创建的文件权限为 (mode & ~umask)。

   FIFO 特殊文件类似于管道,但是创建方式不同。FIFO 特殊文件会通过调用 mkfifo() 进入文件系统。

   一旦通过这种方式创建了一个 FIFO 特殊文件,任何进程都可以像操作普通文件一样打开它进行读写。但是,在进行任何输入输出操作之前,必须同时在两端打开它。尝试打开 FIFO 进行读取通常会阻塞,直到有其他进程打开相同的 FIFO 进行写入,反之亦然。参见 fifo(7) 中关于非阻塞处理 FIFO 特殊文件的内容。

返回值 mkfifo() 成功时返回 0。失败时返回 -1(此时 errno 被适当地设置)。

错误 EACCES pathname 中的一个目录不允许搜索(执行)权限。

   EDQUOT 用户在文件系统上的磁盘块或索引节点配额已经耗尽。

   EEXIST pathname 已经存在。这包括 pathname 是符号链接(悬空或非悬空)的情况。

   ENAMETOOLONG
          要么 pathname 的总长度超过了 PATH_MAX,要么其中一个文件名组件的长度超过了 NAME_MAX。在 GNU 系统中,文件名长度没有强制限制,但一些文件系统可能对文件名组件的长度施加限制。

   ENOENT pathname 中的一个目录组件不存在或者是一个悬空的符号链接。

   ENOSPC 目录或文件系统没有足够的空间用来创建新文件。

   ENOTDIR
          pathname 中的一个组件用作目录,但事实上不是目录。

   EROFS  pathname 指向的是一个只读文件系统。
#pragma once

#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_FILE  "./myFileName"

 我们先自定义头文件comm.hpp这个头文件,我们在这里头文件创建命名管道 client.cc和server.cc这个两个源文件同时包含。就能看到同一个文件->命名管道。

创建命名管道 

#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_FILE  "./myFileName"
#define MODE 0664

//退出码
enum
{
    FIFO_CREATE_ERR = 1  //管道创建失败退出码设置为1

};
#include "comm.hpp"
using namespace std;
// 创建管道
int main ()
{
    int n = mkfifo(FIFO_FILE,MODE);
    if(n == -1)
    {
        perror("mkfifo");
        exit(FIFO_CREATE_ERR);
    }
    return 0;
}

 我们用sever来试试能不能创建创建管道。

 

管道创建好了是不是就能进行通信了? 当然不行,我们现在只是让它们看到同一份资源。

那如何通信?别忘了linux下一切皆文件,当然我们的命名也是文件,那我们就可以利用之前的文件操作的那一套进行通信。open read write close 就是这么的简单。

下面我让client写,server读。 

#include "comm.hpp"
using namespace std;
int main ()
{
    int fd = open(FIFO_FILE,O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        exit(FIFO_OPEN_ERR);
    }

    
    //客户端写入
    string line;
    while(true)
    {
        cout << "Please Enter@ ";
        getline(cin , line);
        int x = write(fd,line.c_str(),line.size());
        
    }
    close(fd);
    return 0;
}

#include "comm.hpp"
using namespace std;
// 创建管道
int main ()
{
  
    //创建管道
    int n = mkfifo(FIFO_FILE, MODE);
    if (n == -1)
    {
        perror("mkfifo");
        exit(FIFO_CREATE_ERR);
    }
    //打开信道

    int fd = open(FIFO_FILE,O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(FIFO_OPEN_ERR);
    }

    //开始通信

    while(true)
    {
        char buffer[1024] = {0};
        int x = read(fd,buffer,sizeof(buffer));
        if(x > 0)
        {
            buffer[x] = 0;
            cout << "client say# " << buffer << endl;
        }
    }

    close(fd);
    
    return 0;
}

 

 前面server写的创建管道不好,每次在命令端都要手动删除管道。下面进行优化,我们comm这个头文件中写一个创建管道的类 构造函数初始化管道,析构函数删除管道

class Init
{
public:
    Init()
    {
        int n = mkfifo(FIFO_FILE, MODE);
        if (n == -1)
        {
            perror("mkfifo");
            exit(FIFO_CREATE_ERR);
        }
    }
    ~Init()
    {
        int m = unlink(FIFO_FILE);
        if(m == -1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};

其实命名管道的代码比匿名管道代码实现要简单的多。作为一名程序员,你写的程序肯定是要报错的,有时候也会其他的情况,告警等等。那有没有一种方法可以知道我们的程序在哪里出错了,什么时间出错的?出错的原因是什么?并把这些信息写入一个文件中方便我们查看。有的 日志

日志 

 可变参数列表

关于打印日志需要用到可变参数列表那什么是可变参数列表? 

可变参数列表是指函数在定义时允许接受不定数量的参数。在许多编程语言中,包括Python,Java和C++等,都支持可变参数列表的特性。

#include <stdarg.h>
#include <stdio.h>

// 一个接受可变参数列表的函数
void print_arguments(int num, ...) {
    va_list args;  // 定义一个va_list类型的变量
    va_start(args, num);  // 初始化args,指向第一个可变参数

    for (int i = 0; i < num; i++) {
        int value = va_arg(args, int);  // 获取下一个可变参数的值
        printf("%d ", value);
    }

    va_end(args);  // 结束args的使用
    printf("\n");
}

int main() {
    print_arguments(3, 10, 20, 30);
    // Output: 10 20 30
    return 0;
}

 获取时间的函数locatime

localtime函数是C和C++标准库time.h中的一个函数,用于将日历时间(秒数)转换为本地时间的struct tm结构体。struct tm结构体表示了时间的各个部分,如年、月、日、小时、分钟和秒等。

以下是localtime函数的声明和简单示例:

struct tm *localtime(const time_t *timer);
  • timer:指向要转换的日历时间的指针。

示例:

#include <time.h>
#include <stdio.h>

int main() {
    time_t current_time;
    struct tm *local_time;

    time(&current_time);
    local_time = localtime(&current_time);
    printf("Current local time: %d-%d-%d %d:%d:%d\n", 
           local_time->tm_year + 1900, local_time->tm_mon + 1, local_time->tm_mday,
           local_time->tm_hour, local_time->tm_min, local_time->tm_sec);
    return 0;
}

 snprintf函数

snprintf函数是C语言中的一个格式化字符串输出函数,它可以控制输出字符的数量,避免缓冲区溢出的风险。该函数的作用类似于printf,但是它可以指定输出的最大长度。

以下是snprintf函数的声明和简单示例:

int snprintf(char *str, size_t size, const char *format, ...);
  • str:指向用于存储输出的字符数组的指针。
  • size:输出的字符数目(包括结尾的空字符)。
  • format:格式化字符串,用来指定输出的格式。
  • ...:可变数量的参数,根据format字符串的具体内容而定。

示例:

#include <stdio.h>

int main() {
    char buffer[50];
    int num = 123;
    snprintf(buffer, 50, "The number is %d\n", num);
    printf("Output: %s", buffer);
    return 0;
}

 vsnprintf函数

snprintf函数将格式化后的字符串输出到buffer数组中,最多输出50个字符。即使格式化后的字符串超过了50个字符,snprintf也会在50个字符后停止输出,避免了缓冲区溢出的问题。

通过使用snprintf可以更加安全地进行字符串格式化输出,特别是在处理用户输入等可能带有风险的数据时,可以有效防止缓冲区溢出漏洞。

vsnprintf函数是C语言中的一个格式化字符串输出函数,它类似于snprintf,允许用户指定输出字符的最大数量。与snprintf不同的是,vsnprintf函数使用一个va_list类型的参数来传递可变数量的参数。

以下是vsnprintf函数的声明和简单示例:

#include <stdio.h>
#include <stdarg.h>

int vsnprintf(char *str, size_t size, const char *format, va_list ap);
  • str:指向用于存储输出的字符数组的指针。
  • size:输出的字符数目(包括结尾的空字符)。
  • format:格式化字符串,用来指定输出的格式。
  • apva_list类型的参数列表。

示例:

#include <stdio.h>
#include <stdarg.h>

void format_string(char *buffer, size_t size, const char *format, ...) {
    va_list args;
    va_start(args, format);
    vsnprintf(buffer, size, format, args);
    va_end(args);
}

int main() {
    char buffer[50];
    format_string(buffer, 50, "The number is %d\n", 123);
    printf("Output: %s", buffer);
    return 0;
}

我们定义了一个辅助函数format_string,它使用vsnprintf来格式化字符串并输出到buffer数组中。通过使用va_list类型的参数列表,vsnprintf可以接受可变数量的参数,更加灵活地进行字符串格式化输出。

vsnprintf函数通常用于创建可变参数的格式化字符串输出函数,可以在实现自定义格式化输出时使用,以提供更加灵活和安全的字符串处理能力。

有了这些基础我们就可以写日志了

​
#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.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 printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    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);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};

​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1638977.html

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

相关文章

八大排序详解:动图、代码、注释

目录 何为八大排序&#xff1f; 直接插入排序 排序过程解读 直接插入排序的特性总结&#xff1a; 希尔排序 希尔排序的特性总结&#xff1a; 直接选择排序 直接选择排序的特性总结&#xff1a; 堆排序 直接选择排序的特性总结&#xff1a; 冒泡排序 快速排序 1.Hoa…

全景剖析阿里云容器网络数据链路(七):Terway DataPath V2(Terway≥1.8.0)

作者&#xff1a;余凯 前言 近几年&#xff0c;企业基础设施云原生化的趋势越来越强烈&#xff0c;从最开始的IaaS化到现在的微服务化&#xff0c;客户的颗粒度精细化和可观测性的需求更加强烈。容器网络为了满足客户更高性能和更高的密度&#xff0c;也一直在高速的发展和演…

【JavaEE网络】网络编程及其应用概述

目录 面向字节流粘包问题 TCP异常情况TCP/UDP对比 网络层重点协议IP协议IP地址 面向字节流 粘包问题 在面向字节流的情况下&#xff0c;会产生一些其他的问题&#xff1a;粘包问题&#xff0c;这里“粘”的是“应用层数据报”&#xff0c;通过TCP read/write的数据&#xff0…

jvm 马士兵 01

01.JVM是什么 JVM是一个跨平台的标准 JVM只识别class文件&#xff0c;符合JVM规范的class文件都可以被识别

javaScript 判断闰年

接受用户输入年份 如果是闰年就弹出闰年&#xff0c;遇到平年就是弹出平年 var a prompt(请输入年份);if(a%40&&a%100!0||a%4000){alert(闰年);}else{alert(平年);}

智慧校园为师生带来的那些帮助

随着互联网技术的发展&#xff0c;学校高度重视校园信息化建设&#xff0c;越来越多的学校开始建设智能校园。智慧校园是以智慧校园建设为基础&#xff0c;为学生的校园生活和学校的日常管理带来生机和活力。 那么&#xff0c;在当代环境下建设智慧校园的必要性是什么呢&#x…

Java集合框架-容器源码分析

Java集合框架-容器&源码分析 文章目录 Java集合框架-容器&源码分析[TOC](文章目录)前言一、集合框架概述二、Collection接口及其子接口(List/Set)及实现类2.1 Collection接口中方法2.2 遍历&#xff1a;Iterator迭代器接口&foreach(5.0新特性)2.3 Connection子接口…

Java中使用Redis实现分布式锁的三种方式

1. 导语 随着软件开发领域的不断演进,并发性已经成为一个至关重要的方面,特别是在资源跨多个进程共享的分布式系统中。 在Java中,管理并发性对于确保数据一致性和防止竞态条件至关重要。 Redis作为一个强大的内存数据存储,为在Java应用程序中实现分布式锁提供了一种高效的…

WSL2连接Windows主机的Mysql

文章目录 需求查看主机IP防火墙设置Mysql设置允许远程连接WSL2连接Mysql 需求 在WSL2&#xff08;本机Ubuntu20.04&#xff09;运行的程序需要将数据写入到本机的Mysql服务器中 查看主机IP 两种办法&#xff1a; Windows主机输入 ipconfig&#xff0c;找到带有WSL后缀的部分…

第13章 软件测评相关标准

一、标准化概述 &#xff08;一&#xff09;概念 1、标准 一定范围内获得最佳秩序&#xff0c;经协商一致并由公认机构批准共同使用和重复使用的一种规范性文档&#xff0c;是标准化活动的核心产物。 2、标准化 一定范围内获得最佳秩序&#xff0c;对现实问题和潜在问题制…

ctfshow——SSRF

文章目录 web 351web 352web 353web 354web 355web 356web357web 358web 359web 360 SSRF(Server-Side Request Forgery&#xff1a;服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下&#xff0c;SSRF攻击的目标是从外网无法访问的内部系统…

Java | Leetcode Java题解之第64题最小路径和

题目&#xff1a; 题解&#xff1a; class Solution {public int minPathSum(int[][] grid) {if (grid null || grid.length 0 || grid[0].length 0) {return 0;}int rows grid.length, columns grid[0].length;int[][] dp new int[rows][columns];dp[0][0] grid[0][0]…

【C/C++基础实战】:用C++实现通讯录管理系统——含完整源码

文章目录 通讯录管理系统一、系统需求以及成品演示二、代码实现三、完整代码 通讯录管理系统 一、系统需求以及成品演示 1.1 系统需求 通讯录是一个可以记录亲人、好友信息的工具。这里利用C来实现一个通讯录管理系统 系统中需要实现的功能如下&#xff1a; 添加联系人&am…

【C语言】/*C语言常见概念*/

目录 前言 一、C语言是什么 二、初识编译和链接 三、什么是可执行程序 四、什么是编译器 五、什么是集成开发环境 六、mian函数的特点 七、什么是关键字 八、标识符的命名规则是什么 九、字符和ASCII码表 十、字符串和\0 十一、转义字符 十二、注释 前言 本篇文章…

[蓝桥杯2024]-PWN:fd解析(命令符转义,标准输出重定向,利用system(‘$0‘)获取shell权限)

查看保护 查看ida 这里有一次栈溢出&#xff0c;并且题目给了我们system函数。 这里的知识点没有那么复杂 方法一&#xff08;命令转义&#xff09;&#xff1a; 完整exp&#xff1a; from pwn import* pprocess(./pwn) pop_rdi0x400933 info0x601090 system0x400778payloa…

力扣刷题第0天:只出现一次的数字

目录 第一部分:题目描述 ​第二部分:题目分析 第三部分:解决方法 3.1思路1: 双指针暴力求解 3.2 思路2&#xff1a;异或运算 第四部分:总结收获 第一部分:题目描述 第二部分:题目分析 由图片分析可得&#xff0c;该题目对算法时间复杂度有一定的要求时间复杂度为O(N)&a…

VBA数据库解决方案第十讲:Recordset记录集合的动态查询显示结果

《VBA数据库解决方案》教程&#xff08;版权10090845&#xff09;是我推出的第二套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;是学完字典后的另一个专题讲解。数据库是数据处理的利器&#xff0c;教程中详细介绍了利用ADO连接ACCDB和EXCEL的方法…

爬虫学习:基本网络请求库的使用

目录 一、urllib网络库 1.urlopen()方法 2.request方法 二、requests网络请求库 1.主要方法 2.requests.get()和requests.post() 一、urllib网络库 1.urlopen()方法 语法格式&#xff1a; urlopen(url,data,timeout,cafile,capath,context) # url:地址 # data:要提交的数据…

编程题库-Python、Java、C++、C 应有尽有!!!

目录 网址注册账号题库 网址 传送门 http://oj.ecustacm.cn/ 这个↑链接是网站 注册账号 刚进去是这个页面 注册一个账号 题库 点击上方的问题菜单&#xff0c;进入题库 点击题目标题进入题目&#xff0c;我就随便点一道 这里面一般会有样例输入和输出以及题目描述 点…

网络安全 SQLmap-tamper的使用

目录 使用SQLmap Tamper脚本 1. 选择合适的Tamper脚本 2. 在命令行中使用Tamper脚本 3. 组合使用Tamper脚本 4. 注意和考虑 黑客零基础入门学习路线&规划 网络安全学习路线&学习资源 SQLmap是一款强大的自动化SQL注入和数据库取证工具。它用于检测和利用SQL注入漏…