【IPC】管道通信【命名管道】

文章目录

1.管道小总结

linux-manualshouce

在Linux中,manual手册的编号用于区分手册的不同部分。这些编号通常用于man命令中,以便用户可以指定要查看的手册部分。man命令的编号对应关系如下:

用户命令(User Commands):通常包括可执行程序或shell命令。

系统调用(System Calls):由内核提供的函数。

库调用(Library Calls):程序库中的函数。

特殊文件(Special Files):通常位于/dev目录下的设备文件。

文件格式和约定(File Formats and Conventions):例如/etc/passwd等配置文件的格式。

游戏(Games)。

杂项(Miscellaneous):包括宏包和约定等其他内容。

系统管理命令(System Administration Commands and Daemons)。

此外,还有第9个部分,通常用于其他内容,比如内核例行程序(Kernel Routines)。

管道读写规则

  1. 当没有数据可读时
    O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据为止。
    O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  2. 当管道满的时候
    O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
    O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  3. 如果所有管道写端对应的文件描述符被关闭,则read返回0
  4. 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
  5. 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  6. 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

原子性

要么做要么不做。无中间状态。

多执行流下数据出现并发访问时考虑。

2.命名管道

2.1认识命名管道

  1. 进程A打卡了文件file,进程B再去打开file时,OS识别到file已打开,则不再创建对应的file结构体,而是直接指向已有的file结构体。【OS中一般都会存在n多个进程,且存在像许多进程打开同一个文件的情况,如果对于已经打开的文件,另外的进程再去打开他时区创建一个新的file结构体,这无疑是巨大的时空浪费】
  2. 命名管道无疑是这样的一个存在,首先他是一个文件,其次他不用将数据刷新到磁盘。
  3. 该文件一定在系统路径中(路径具有唯一性)。该文件有名字,可以被打开,但是不会将内存数据刷新到磁盘。
  4. 至此,两个毫不相干的进程通过命名管道文件的路径看到同一份资源。【匿名管道通常应用于有血缘关系的进程,这也是匿名管道的一个缺点】

2.2命名管道的应用小场景

创建命名管道


场景1

场景2:输出重定向

条件编译
#ifndef _COMMON_H_
#define _COMMON_H_
#endif

这三句代码是C或C++编程语言中常用的预处理器指令,它们通常用于头文件中以防止头文件内容的多次包含(多重定义问题)。我来详细解释每句代码的含义:

#ifndef COMMON_H

这是一个条件编译指令,它检查是否定义了名为_COMMON_H_的宏(macro)。如果_COMMON_H_没有被定义,那么紧随其后的代码(直到遇到#endif或另一个条件编译指令)会被编译器包含(或编译)。

  1. #define COMMON_H

这行代码定义了一个名为_COMMON_H_的宏。一旦这个宏被定义,再次遇到#ifndef _COMMON_H_时,由于_COMMON_H_已经被定义,所以其后的代码不会被再次包含。

  1. #endif

这是一个结束标记,表示#ifndef条件编译指令的结束。

将这三行代码放在头文件common.h的开始处,可以确保无论这个头文件被包含多少次,其内部的内容都只会被编译器编译一次。这在处理循环包含或者复杂项目结构时非常有用,因为你可以避免函数或变量被多次定义,从而避免编译错误。

这样,当common.h首次被包含时,其内容会被编译;如果再次被包含,由于_COMMON_H_已经被定义,其内容不会被再次编译。

unlink()删除文件

rm和unlink的异同

在Linux中,rm和unlink都用于删除文件或目录,但它们在使用方式和功能上有一些重要的区别。

  1. 命令形式和使用方式:

rm 是一个更高级的、用户友好的命令,用于删除文件或目录。它可以处理多种复杂的情况,例如删除目录(使用 -r 或 -R 选项)或强制删除文件(使用 -f 选项)。

unlink 是一个较低级的系统调用,通常用于在编程中删除文件。在shell中,你可以使用 unlink 命令来删除文件,但它没有 rm 那么多的选项和功能。

  1. 功能和行为:

在功能上,rm 和 unlink 基本上是一样的:它们都删除了指定的文件或目录。然而,由于 rm 提供了更多的选项,它可以更灵活地处理各种情况。

在行为上,rm 和 unlink 都减少了文件的链接计数。如果一个文件只有一个链接(即它是唯一被引用的),那么当这个链接被 rm 或 unlink 删除后,该文件占用的磁盘空间也会被释放。但是,如果一个文件有多个链接,那么删除其中一个链接只会减少链接计数,而不会释放磁盘空间。

  1. 错误处理:

rm 在删除不存在的文件或目录时,会输出一个错误消息。然而,如果你使用 -f 选项,rm 会静默地忽略这些错误。

unlink 在遇到错误时,通常会返回一个错误代码,但具体的行为可能取决于你的shell和如何调用 unlink。

总的来说,rm 和 unlink 在功能上相似,但在使用方式和行为上有所不同。对于日常的文件删除任务,rm 通常是一个更好的选择,因为它提供了更多的选项和更友好的错误处理。而在编程中,你可能需要更直接地控制文件删除的过程,这时 unlink 可能更合适。

2.3模拟命名管道

1.Lod.hpp

cpp 复制代码
#ifndef _LOG_H_
#define _LOG_H_

#include <iostream>
#include <ctime>

#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3

const std::string tip[] ={
        "Debug",
        "Notice",
        "Warning",
        "Error"};

std::ostream &Log(std::string message, int option)
{
    // 获取时间戳 time_t timestamp; time(timestamp);
    time_t timestamp = time(nullptr);
    if (timestamp == std::time_t(-1))
    {
        std::cerr << "获取时间失败" << std::endl;
        exit(1);
    }

    // 获取格式化时间 tm *localtime(const time_t *__timer)
    tm *timeinfo = std::localtime(&timestamp);

    std::cout << " | "
              << 1900 + timeinfo->tm_year << "-"
              << 1 + timeinfo->tm_mon << "-"
              << timeinfo->tm_mday << " "
              << timeinfo->tm_hour << ":"
              << timeinfo->tm_min << ":"
              << timeinfo->tm_sec
              << " | "
              << tip[option]
              << " | "
              << message;

    return std::cout;
}

#endif

2.common.hpp

cpp 复制代码
#ifndef _COMMON_H_
#define _COMMON_H_

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iomanip> 
#include "Log.hpp"

using namespace std;

#define MODE 0666
#define SIZE 128

string ipcPath = "./fifo.ipc";

#endif

3.server.cxx

cpp 复制代码
#include "common.hpp"
#include <sys/wait.h>

static void IpcWithClient(int fd)
{
    char buffer[SIZE];
    while (true)
    {
        memset(buffer, '\0', sizeof(buffer));
        // 【OS写的时候不写\0 这里读的时候自然没有\0 我们空一个以免有需要在末尾加0】
        // 当然 上面的memset已经对所有单元都加了\0 这里只是通用的默认规范
        ssize_t s = read(fd, buffer, sizeof(buffer) - 1);

        if (s > 0) // read success
        {
            cout << "[" << getpid() << "] "
                 << "client say:> " << buffer << endl;
        }
        else if (s == 0) // end of file
        {
            cerr << "[" << getpid() << "] "
                 << "read end of file, clien quit, server quit too!" << endl;
            break;
        }
        else // read error
        {
            perror("IpcWithClient::read");
            break;
        }
    }
}

int main()
{
    // 1. 创建管道文件 int mkfifo(const char *__path, mode_t __mode)
    if (mkfifo(ipcPath.c_str(), MODE) < 0) //成功返回0 失败返回-1
    {
        perror("server::mkfifo");
        exit(1);
    }
    Log("创建管道文件成功", Debug) << " step 1" << endl;

    // 2. 打开管道文件
    int fd = open(ipcPath.c_str(), O_RDONLY);
    if (fd < 0)
    {
        perror("server::open");
        exit(2);
    }
    Log("打开管道文件成功", Debug) << " step 2" << endl;

    // 3. 开始进行IPC 服务端创建子进程去与客户端进行ipc
    int serverChildNum = 3;
    for (int i = 0; i < serverChildNum; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            IpcWithClient(fd);
            exit(1);
        }
    }
    //阻塞式等待子进程 获取子进程退出状态 回收子进程
    for (int i = 0; i < serverChildNum; i++)
    {
        waitpid(-1, nullptr, 0);
    }

    // 4. 关闭文件
    close(fd);
    Log("关闭管道文件成功", Debug) << " step 3" << endl;

    unlink(ipcPath.c_str()); // int unlink(const char *__name)
    Log("删除管道文件成功", Debug) << " step 4" << endl;

    return 0;
}

4.client.cxx

cpp 复制代码
#include "common.hpp"

int main()
{
    // 1. 获取管道文件 创建文件由server负责
    // 这里不加O_TRUNC 文件不存在client也不创建 失败是server的事情
    int fd = open(ipcPath.c_str(), O_WRONLY);
    if (fd < 0)
    {
        perror("client::open");
        exit(1);
    }

    // 2. 进行ipc
    string buffer;
    while (true)
    {
        cout << "Please input information:> ";
        //istream& getline<char, char_traits<char>, allocator<char>>(istream& __is, string& __str)
        std::getline(std::cin, buffer);
        write(fd, buffer.c_str(), buffer.size());
    }

    // 3. 关闭
    close(fd);
    return 0;
}

3.管道代码总结

  1. 模拟匿名管道: 父进程调用pipe() 创建匿名管道 记录pipefd 父子进程各自关闭不用的pipefd 之后父进程写到写端 子进程从读端读
  2. 模拟进程池: 父进程创建N个子进程 对每一个子进程之开启读端 父进程只开启写端 父进程随机选择一个子进程去执行它写到写端的命令 子进程阻塞等待获取从读端的命令 直到获取成功 否则一直阻塞等待
  3. 模拟命名管道:服务端进程创建命名管道 让子进程从fd里读信息后输出到显示器 客户端打开彼管道文件 向该fd写信息
相关推荐
学Linux的语莫13 分钟前
Ansible使用简介和基础使用
linux·运维·服务器·nginx·云计算·ansible
一只小小汤圆20 分钟前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
踏雪Vernon22 分钟前
[OpenHarmony5.0][Docker][环境]OpenHarmony5.0 Docker编译环境镜像下载以及使用方式
linux·docker·容器·harmonyos
Onlooker12923 分钟前
云服务器部署WebSocket项目
服务器
学Linux的语莫36 分钟前
搭建服务器VPN,Linux客户端连接WireGuard,Windows客户端连接WireGuard
linux·运维·服务器
legend_jz40 分钟前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
Komorebi.py41 分钟前
【Linux】-学习笔记04
linux·笔记·学习
黑牛先生43 分钟前
【Linux】进程-PCB
linux·运维·服务器
Karoku0661 小时前
【企业级分布式系统】ELK优化
运维·服务器·数据库·elk·elasticsearch
嘿BRE1 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++