匿名管道与命名管道

news2024/10/7 18:20:33

匿名管道与命名管道

  • 一,进程间通信
    • 什么是进程间通信
    • 进程间通信的目的
    • 管道的概念
  • 二,匿名管道
    • 匿名管道的创建
    • 匿名管道使用
    • 匿名管道的特性以及四种场景
    • 匿名管道的原理
    • 通过匿名管道实现简易进程池。
  • 三,命名管道
    • 命名管道的创建
    • 命名管道的使用
    • 命名管道实现简易的进程池
  • 管道总结

一,进程间通信

什么是进程间通信

🚀进程间通信就是让两个进程进行交流,但是我们知道进程具有独立性,每个进程OS都会为其维护一个pcb,一个进程中的数据在另一个进程中是看不到的,那怎么实现进程间通信的呢?
🚀两个进程要想通信,首先它们需要看到同一份资源,其实就是操作系统出面,为它们提供了一块公共的资源,就是一段内存,可能以文件的方式提供,也可能就是提供的原始的内存块。

进程间通信的目的

🚀数据传输 :一个进程需要将它的数据发送给另一个进程。
🚀资源共享 :多个进程之间共享同样的资源。
🚀通知事件 :一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
🚀进程控制 :有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

管道的概念

🚀 管道是一种古老的进程间通信的方式,最早出现于Unix系统种。我们把一个进程连接到另一个进程的一个数据流称为一个“管道”。
🚀当前使用过Linux操作系统的应该都使用过管道,例如,我们用两个指令的组合来算出一个文本有多少行。

cat test.txt | wc -l

在这里插入图片描述
🚀管道就是OS通过以文件的形式,给两个进程创建一份共享资源。其实就是两个进程公用一块内核缓冲区,一个进程从里面读取数据每一个进程往其中写数据,进而达到通信的效果。

二,匿名管道

匿名管道的创建

🚀首先介绍一个系统接口pipe

int pipe(int pipefd[2]);

这个系统调用的功能就是创建一个匿名管道,注意到它的参数是一个int类型的数组,数组有两个元素,其实这是两个输出型参数pipefd[0] 是以读方式打开管道文件返回的文件描述符,pipefd[1] 是以写方式打开管道文件返回的文件描述符。
如果创建成功0会被返回,反之-1被返回。
🚀创建了匿名管道之后,再通过fork系统调用创建子进程,这样子进程就会继承来自父进程的文件描述符表,那么子进程也会看到这个管道文件,从而达到两个进程看到同一份资源。对于一个进程来说只能从这一管道文件读或者是写,不能既读又写,所以我们创建子进程后要关闭父进程与子进程的多余的文件描述符。

匿名管道使用

一般步骤:
🚀使用pipe创建管道文件
🚀使用fork创建子进程
🚀父子进程关掉相应的文件描述符
🚀父子进程间实现通信
🚀通信结束后关掉文件描述符
🚀对于命名管道还要使用unlink将磁盘文件删除

#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <cassert>

int main()
{
    // 1.创建管道
    int pipe_fd[2] = {0};
    int res = pipe(pipe_fd);
    if (res < 0)
    {
        perror("pipe");
        exit(errno);
    }
    // 2.创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        exit(errno);
    }
    if (id == 0) // child
    {
        // 3.关闭相应的fd
        close(pipe_fd[0]);
        int cnt = 0;
        std::string str = "我是子进程我正在向管道中写";
        char buffer[1024] = {'\0'};
        while (true)
        {
            //char ch = 'Y';
            snprintf(buffer, sizeof(buffer), "cnt = %d,%s", cnt++, str.c_str());
            //std::cout << "子进程正在写第" << cnt++ << "次" << std::endl;
            int n = write(pipe_fd[1], buffer, strlen(buffer));
            //t n = write(pipe_fd[1], &ch, 1);
            assert(n >= 0);
            (void)n;
            sleep(1);
        }
        //5.通信结束后关掉相应的文件描述符
        close(pipe_fd[1]);
        exit(0);
    }
    // parent
    //  3.关闭相应的fd
    close(pipe_fd[1]);
    //4.进程间通信
    char buffer[1024] = {'\0'};
    while (true)
    {
        int n = read(pipe_fd[0], buffer, sizeof(buffer) - 1);
        assert(n >= 0);
        (void)n;
        std::cout << "我是父进程,我从子进程中读取的数据是: " << buffer << std::endl;
        //sleep(20);
        sleep(1);
    }
    //5.通信结束后关掉相应的文件描述符
    close(pipe_fd[0]);
    return 0;
}

这段代码就是严格按照上面的五个步骤,其中由于管道是单项通信的,这段代码中子进程保留了写端,父进程保留了读端。达到了父子进程间的数据传输。

在这里插入图片描述

匿名管道的特性以及四种场景

特点:
🚀管道实现的是单向通信,具有半双工的特点。
在这里插入图片描述
🚀管道的本质是文件,由于fd的声明周期是随进程的—>管道的声明周期是随进程的

管道文件是一个内存级的缓冲区,不会将数据刷新到磁盘上。其本质就是在内核中创建的struct file结构体,以读方式打开管道文件的进程将数据或者是命令写入struct file中的内核级缓冲区,以读方式打开管道文件的进程,从缓冲区中,将数据读出去。每个文件都有预期对应的inode结构体,而inode结构体中有两个计数器-i_count-i_link,这两个分别是针对内存上和磁盘上的,当有进程打开文件的时候i_count就会加1,而close的时候会减1,当减到0的时候在内核中创建的struct file结构体就会被OS回收掉,而i_link是指这个文件的连接数,使用unlink函数或者指令可以使i_link这个计数器减1,同样的在减到0的时候,这个文件就会被在磁盘上删除。而对于匿名管道如果所有的进程都切断了与struct file的关系,也就是i_count减为0,此时struct file会被OS回收,并且在磁盘上的inode结构体等也会被删除。

在这里插入图片描述
🚀匿名管道通信通常是有血缘关系的进程间通信的,例如父子进程,兄弟进程等。

匿名管道之所以能够通信就是因为,子进程继承了父进程的文件描述表,从而看到了相同的struct file结构体,进而能够进行进程间的通信,那么爷孙进程能够通信吗?答案是肯定的,父进程中的文件描述表继承自它的父进程,子进程又继承了父进程的文件描述符表,所以爷孙进程是可以看到同一份资源的,进而爷孙进程间是可以通信的。
对于兄弟进程,由于兄弟进程都继承了来自它们父进程的文件描述符表,所以它们同样可以看大一份相同的资源,从而可以实现进程间的通信。

🚀在管道通信中,写入的次数与读取的次数不是严格匹配的,表现为字节流。
🚀管道具有一定的协同能力,能让读端与写端按照一定的规则进程通信,是自带同步机制的。

管道通信的四种场景
🚀如果读端读完了所有数据,写端没有继续写,那么读端只能等待。

就用上面那份代码做一个实验:上面的代码中子进程是写端父进程是读端,我们让子进程写完一次数据后休眠十秒,这期间让父进程一直从管道中读取数据。

在这里插入图片描述
在这里插入图片描述

父进程每读出一条数据够要等上几秒中才能从管道中读取出下一条数据,这就很好的验证了,如果读端将管道中的数据都读取了出来,而写端并没有写入,这一期间读端会一直等待。

🚀如果读端不读写端一直写入,那么将管道写满后就不能再写入了。

让子进程每次写入一个字节的数据,而父进程休眠上30秒,在这期间已经足够让子进程把管道写满,同时还可以在子进程写入时记录一下次数。

在这里插入图片描述

可以看到,子进程写入了65000多次的时候,就不能再写入了说明此时管道已经被写满了,同时也从侧面证明了管道的大小大约是4KB。

🚀如果关闭了写端,读取完毕管道数据,再读就会返回0,表面那个读到了文件结尾。
在这里插入图片描述
在这里插入图片描述

可以看到,杀死写端的子进程后,父进程在读取的完管道中的数据后,read的返回值变为0,并且重复读到的都是管道中最后的那条数据。

🚀如果写端一直写,读端关闭,那么写端的进程就会被OS杀死,因为OS不会维护没有意义,低效率或者是浪费资源的事情。简单说就是如果读端关闭那么OS会杀死写端的进程。

如果匿名管道的读端关闭,那么OS会给写端的进程发送SIGPIPE这个信号杀死写端进程,为了验证这一结论,父进程读取一条数据后休眠10秒休眠后close掉读端的fd,然后waitpid等待子进程读取子进程获取到的信号。

while (true)
    {
        int n = read(pipe_fd[0], buffer, sizeof(buffer) - 1);
        assert(n >= 0);
        (void)n;
        std::cout << "read的返回值:" << n << "我是父进程,我从子进程中读取的数据是: " << buffer << std::endl;
        sleep(10);
        break;
        //sleep(1);
    }
    //5.通信结束后关掉相应的文件描述符
    close(pipe_fd[0]);
    int status = 0;
    waitpid(id,&status,0);
    std::cout << "singal : " << (status & 0x7f) << std::endl;

在这里插入图片描述
在这里插入图片描述

当读端关闭后,OS会给写端的进程发送13号SIGPIPE信号杀死该进程。

匿名管道的原理

🚀匿名管道就是一块内核缓冲区(在struct file内)。
🚀并且匿名管道文件对应的struct file是不会进行刷盘操作。

进程间通信就是要让不同的进程看到相同的资源,而使用匿名管道这种通信方式,是子进程继承了来自父进程的文件描述符表,从而和父进程看到了同一块资源,达到进程间通信的效果。

在这里插入图片描述

通过匿名管道实现简易进程池。

🚀让父进程通过fork系统调用创建多个子进程,并且父进程与每个子进程间都会通过匿名管道来进行进程间通信,通过匿名管道父进程给子进程发配任务信号,从而使子进程完成相应的任务。
在这里插入图片描述
步骤:
🚀首先循环创建出多个匿名管道和多个子进程
🚀将每个子进程与某个匿名管道建立起联系
🚀父进程关闭相应读端,子进程关闭相应写端
🚀通过进程间通信达到父进程对子进程的控制
🚀使用结束后关闭相应的文件描述符

在Task.hpp中创建相应的任务。

#pragma once
#include <iostream>
#include <vector>

#include <unistd.h>
typedef void (*func_t)();
void PrintLog()
{
    std::cout << " pid : " << getpid() << "打印日志任务,正在被执行..." << std::endl;
}
void InsertMysql()
{
    std::cout << " pid : " << getpid() << "访问数据库任务,正在被执行..." << std::endl;
}
void NetRequest()
{
    std::cout << " pid : " << getpid() << "访问网络的任务,正在被执行..." << std::endl;
}
#define COMMAND_LOG 0
#define COMMAND_MYSQL 1
#define CONMAND_REQUEST 2
#define QUIT 3

class Task
{
public:
    Task()
    {
        functions.push_back(PrintLog);
        functions.push_back(InsertMysql);
        functions.push_back(NetRequest);
    }
    void Execute(int index)
    {
        functions[index]();
    }
    ~Task()
    {
    }

private:
    std::vector<func_t> functions;
};

在ctrlProcess.cpp中完成进程的控制

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <cerrno>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <cassert>
#include "Task.hpp"
const int num = 6;
Task t;
class EndPoint
{
public:
    int write_fd;
    pid_t child_pid;
    std::string _name;

public:
    EndPoint(int fd, pid_t id)
        : write_fd(fd), child_pid(id)
    {
        char namebuffer[64];
        snprintf(namebuffer, sizeof(namebuffer), "Process-%d[%d-%d]", number++, child_pid, write_fd);
        _name = namebuffer;
    }
    std::string getname() const
    {
        return _name;
    }
    std::string getname()
    {
        return _name;
    }
    ~EndPoint()
    {
    }

private:
    static int number;
};
int EndPoint::number = 1;
void WaitCommand()
{
    while (true)
    {
        int command = 0;
        int n = read(0, &command, sizeof(command));
        if (n > 0)
        {
            t.Execute(command);
        }
        else if (n == 0)
        {
            break;
        }
        else
        {
            break;
        }
        sleep(1);
    }
}
void CreateProcess(std::vector<EndPoint> &end_points)
{
    std::vector<int> close_array;
    for (int i = 0; i < num; ++i)
    {
        int pipe_fd[2] = {0};
        int n = pipe(pipe_fd);
        if (n < 0)
        {
            perror("pipe");
            exit(errno);
        }
        pid_t id = fork();
        if (id < 0)
        {
            perror("fork");
            exit(errno);
        }
        if (id == 0)
        {
            // 先关掉从父进程那里继承得关于别的管道得写端
            for (auto e : close_array)
            {
                close(e);
            }
            // child
            close(pipe_fd[1]);
            // 输入重定向
            dup2(pipe_fd[0], 0);
            WaitCommand();
            close(pipe_fd[0]);
            exit(0);
        }
        // parent
        close(pipe_fd[0]);
        end_points.push_back(EndPoint(pipe_fd[1], id));
        close_array.push_back(pipe_fd[1]);
    }
}
int select_command()
{
    int command = 0;
    std::cout << "#############################" << std::endl;
    std::cout << "#0.PrintLog   #1.InsertMysql#" << std::endl;
    std::cout << "#2.NetRequest #3.Quit     ###" << std::endl;
    std::cout << "#############################" << std::endl;
    std::cout << "Please select# ";
    std::cin >> command;
    return command;
}
void MakeProcess(std::vector<EndPoint> &end_points)
{
    int cnt = 0;
    while (true)
    {
        // 1.选择任务
        int command = select_command();
        if (command == 3)
            break;
        if (command < 0 || command > 2)
            continue;
        // 2.选择进程---以轮询的方式
        int index = cnt++;
        cnt %= end_points.size();
        std::cout << "父进程选择了" << end_points[index].getname() << " 处理任务" << std::endl;
        // 3.下发任务
        write(end_points[index].write_fd, &command, sizeof(command));
        sleep(1);
    }
}
void RecycleProcess(std::vector<EndPoint> &end_points)
{
    // 依次关闭父进程的写端,子进程就会退出
    for (int i = 0; i < end_points.size(); i++)
    {
        close(end_points[i].write_fd);
        int res = waitpid(end_points[i].child_pid, nullptr, 0);
        std::cout << "父进程回收了 " << end_points[i].child_pid << " 子进程" << std::endl;
        assert(res);
        (void)res;
        sleep(1);
    }
}
int main()
{
    srand((size_t)time(NULL));
    std::vector<EndPoint> end_points;
    CreateProcess(end_points);

    MakeProcess(end_points);

    RecycleProcess(end_points);
    
    return 0;
}

注意一个小问题,我在代码中,没fork出一个子进程后,都要先关掉它从父进程中继承的多余的文件描述符,如果不这样做那么会在你关闭父进程写端的时候会出现bug。下面来解释一下:

在这里插入图片描述

父进程在创建第一个子进程的时候,子进程1会继承第一个管道的读端和写端,然后父进程关闭掉管道1的读端,子进程1关掉管道1的写端。但是父进程在创建第2个管道的时候,第二个子进程不仅会继承了管道2的读写端,还会继承了管道1的写端,同样子进程3会进程了管道1的写端和管道2的写端。这样的话在想结束进程前,关闭父进程的写端,使对应子进程对应得管道得写端关闭那么子进程读完管道内得所有数据后就会退出,但是在父进程关闭管道1得写端得时候,就会出现问题,不仅父进程是管道1得写端,子进程2和子进程3都是父进程得写端,导致waitpid这段代码一直待等待子进程1的退出,而一直卡在这。

int res = waitpid(end_points[i].child_pid, nullptr, 0);

在这里插入图片描述
🚀解决的方法就是在创建一个新的子进程的时候,首先关掉继承自父进程关于其他管道的写端的文件描述符,达到一个匿名管道文件的写端只有父进程。

演示:
在这里插入图片描述

选择任务始于用户交互式的,发配任务是让子进程轮询的去执行内务的。

三,命名管道

命名管道的创建

🚀由于匿名管道只支持有血缘关系的进程实现通信,难道两个毫不相干的进程就无法实现通信嘛?答案是否定的,没有血缘关系的两个进程可以通过命名管道的方式实现进程间的通信。
🚀命名管道与匿名管道的区别就是命名管道有名字,可以被你看到,可以通过mkfifo指令创建一个命名管道。
在这里插入图片描述

在这里插入图片描述

值得注意的是即使你向管道文件中写了数据,你仍然会看到,管道文件的大小是0,这是因为命名管道文件在磁盘上没有其对应的data block数据块,意味着它里面的数据都是在内存上的不会冲刷到磁盘上。

🚀使用mkfifo函数创建命名管道

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

第一个参数是命名管道的名字,第二个参数是创建命名管道的权限。如果创建成功返回0,否则返回-1。
注意: 第二个参数的权限是要与umask掩码运算后得到最终的权限的。

在这里插入图片描述

命名管道的使用

🚀首先创建出命名管道
🚀一个进程以读方式打开该管道,另一个进程以写方式打开该管道
🚀实现进程间通信
🚀通信结束后关闭相应的文件描述符
🚀在关闭文件描述符后使用unlink函数删除管道文件(关闭文件描述符是让其i_count计数器为0,使用unlink是让其i_link计数器为0 ,是这个文件在磁盘上被删除)

comm.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <cerrno>
#include <cstdlib>
#include <fcntl.h>
#include <curses.h>
#include <cstring>
using namespace std;
string file_name = "fifo";

server.cpp

#include "comm.hpp"
int main()
{
    // 1.创建命名管道
    int n = mkfifo(file_name.c_str(), 0664);
    if (n == -1)
    {
        cerr << errno << strerror(errno) << endl;
        return 1;
    }
    // 2.打开管道文件
    int res = open(file_name.c_str(), O_RDONLY);
    if (res == -1)
    {
        perror("open");
        return 2;
    }
    // 3.通信
    while (true)
    {
        char buffer[1024];
        int ret = read(res, buffer, sizeof(buffer) - 1);
        // int ret = read(res, &buffer[0], sizeof(char));
        if (ret > 0)
        {
            buffer[ret] = '\0';
            cout << buffer << endl;
            // fflush(stdout);
        }
        else if (ret == 0)
        {
            cout << "写端关闭" << endl;
            break;
        }
        else
            break;
    }
    // 4.关闭命名管道
    close(res);
    unlink(file_name.c_str());
    return 0;
}

client.cpp

#include "comm.hpp"

int main()
{
    // 客户端打卡命名管道文件
    int n = open(file_name.c_str(), O_WRONLY);
    if (n == -1)
    {
        perror("open");
        return 3;
    }

    //通信
    char buffer[1024];
    memset(buffer, '\0', sizeof(buffer));
    while (true)
    {
        cout << "请输入# ";
        fgets(buffer, sizeof(buffer), stdin);
        buffer[strlen(buffer) - 1] = '\0';
        write(n, buffer, strlen(buffer));
        // system("stty raw");
        // int c = getchar();
        // system("stty -raw");
        // write(n, (char *)&c, sizeof(char));
    }
    // 关闭文件
    close(n);
    return 0;
}

在这里插入图片描述

这段代码实现了客户端输入消息,在服务端可以接收到消息。

命名管道实现简易的进程池

大体的思想与匿名管道实现进程池的思想一致,就是在创建管道,和使不同的进程与管道建立联系的方式不同。
首先在头文件中定义了三个管道文件的名字,在主进程中首先创建这三个管道,并且以写方式打开管道,在其他三个次进程中,都以读方式打开相应的进程,例如Process1就打开1.pipe管道文件。通过主进程给此进程发送任务编号,次进程从管道中读取到信息后,去执行相应的任务。最后在主进程中关闭掉所有的写端,则在次进程中对应的管道写端关闭在其read完所有数据后就会退出,也会关闭掉相应的文件描述符,最终在主进程中使用unlink函数删除掉所有的管道文件。

comm.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdio>
using namespace std;

const int num = 6;
string S[num] =
    {
        "1.pipe",
        "2.pipe",
        "3.pipe"};
// vector<string> s(3);
// void init_s()
// {
//     s[0] = "1.pipe";
//     s[1] = "2.pipe";
//     s[2] = "3.pipe";
// }

typedef void (*func_t)();
void PrintLog()
{
    std::cout << " pid : " << getpid() << "打印日志任务,正在被执行..." << std::endl;
}
void InsertMysql()
{
    std::cout << " pid : " << getpid() << "访问数据库任务,正在被执行..." << std::endl;
}
void NetRequest()
{
    std::cout << " pid : " << getpid() << "访问网络的任务,正在被执行..." << std::endl;
}
#define COMMAND_LOG 1
#define COMMAND_MYSQL 2
#define CONMAND_REQUEST 3
#define QUIT 0

class Task
{
public:
    Task()
    {
        functions.push_back(PrintLog);
        functions.push_back(InsertMysql);
        functions.push_back(NetRequest);
    }
    void Execute(int index)
    {
        functions[index]();
    }
    ~Task()
    {
    }

private:
    std::vector<func_t> functions;
};

ctrlProcess.cpp

#include "comm.hpp"
class EndPoint
{
public:
    EndPoint(int fd, string name)
        : _write_fd(fd), _pipename(name)
    {
    }

public:
    int _write_fd;
    string _pipename;
};

int select_command()
{
    int command = 0;
    std::cout << "#############################" << std::endl;
    std::cout << "#0.PrintLog   #1.InsertMysql#" << std::endl;
    std::cout << "#2.NetRequest #3.Quit     ###" << std::endl;
    std::cout << "#############################" << std::endl;
    std::cout << "Please select# ";
    std::cin >> command;
    return command;
}
int main()
{
    // 1.打开相应的管道。
    for (int i = 0; i < 3; i++)
    {
        int n = mkfifo(S[i].c_str(), 0664);
        if (n == -1)
        {
            perror("mkfifo");
            return 1;
        }
    }
    vector<EndPoint> end_points;
    // 2.打开相应文件
    for (int i = 0; i < 3; i++)
    {
        int n = open(S[i].c_str(), O_WRONLY);
        if (n == -1)
        {
            perror("open");
            return 1;
        }
        end_points.push_back(EndPoint(n, string(S[i])));
    }
    cout << "size:----------" << end_points.size() << endl;
    // 通信
    int cnt = 0;
    while (true)
    {
        // 1.选择任务
        int command = select_command();
        if (command == 3)
            break;
        if (command < 0 || command > 2)
            continue;
        // 2.选择进程
        int index = cnt++;
        // cnt %= end_points.size();
        cnt %= 3;

        // 3.下发任务
        write(end_points[index]._write_fd, &command, sizeof(int));
        sleep(1);
    }
    // 关闭
    for (int i = 0; i < end_points.size(); i++)
    {
        close(end_points[i]._write_fd);
    }
    // 删除pipe文件
    for (int i = 0; i < end_points.size(); i++)
    {
        unlink(end_points[i]._pipename.c_str());
    }
    std::cout << "已经彻底删除了pipe文件" << std::endl;
    return 0;
}

Process1.cpp

#include "comm.hpp"
int main()
{
    Task t;
    // 打开管道文件
    int n = open(S[0].c_str(), O_RDONLY);
    if (n == -1)
    {
        perror("open");
        return 2;
    }
    // 读取任务编号
    int command = 0;
    while (true)
    {
        int res = read(n, &command, sizeof(command));
        if (res > 0)
        {
            t.Execute(command);
        }
        else if (res == 0)
        {
            cout << "写端关闭 " << endl;
            break;
        }
        else
            break;
    }
    close(n);
    return 0;
}

Process2.cpp

#include "comm.hpp"

int main()
{
    Task t;
    // 打开管道文件
    int n = open(S[1].c_str(), O_RDONLY);
    if (n == -1)
    {
        perror("open");
        return 3;
    }
    // 读取任务编号
    int command = 0;
    while (true)
    {
        int res = read(n, &command, sizeof(command));
        if (res > 0)
        {
            t.Execute(command);
        }
        else if (res == 0)
        {
            cout << "写端关闭 " << endl;
            break;
        }
        else
            break;
    }
    close(n);
    return 0;
}

Process3.cpp

#include "comm.hpp"

int main()
{
    Task t;
    // 打开管道文件
    int n = open(S[2].c_str(), O_RDONLY);
    if (n == -1)
    {
        perror("open");
        return 4;
    }
    // 读取任务编号
    int command = 0;
    while (true)
    {
        int res = read(n, &command, sizeof(command));
        if (res > 0)
        {
            t.Execute(command);
        }
        else if (res == 0)
        {
            cout << "写端关闭 " << endl;
            break;
        }
        else
            break;
    }
    close(n);
    return 0;
}

演示:
在这里插入图片描述

管道总结

🚀管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
🚀如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
🚀命名管道是一种特殊类型的文件。
🚀匿名管道由pipe函数创建并打开。
🚀命名管道由mkfifo函数创建,打开用open。
🚀FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

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

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

相关文章

vue3+vite+ts 接入QQ登录

说明 前提资料准备 在QQ互联中心注册成为开发者 站点&#xff1a;https://connect.qq.com/创建应用&#xff0c;如图 js sdk方式 下载对应的sdk包 sdk下载&#xff1a;https://wiki.connect.qq.com/sdk%e4%b8%8b%e8%bd%bd 使用 下载离线js sdk 打开&#xff1a;https:…

jQuery核心

目录 一、引入jQuery 二、jQuery的内涵 1、jQuery挂载在window对象上 2、jQuery是一个函数对象 三、jQuery函数的四种参数形式 1、参数是一个函数function 2、参数是一个选择器 3、参数是一个DOM对象 4、参数是一个HTML元素标签&#xff08;HTML代码&#xff09; 简介…

【Linux】八、Linux进程信号详解(完结)

目录 三、阻塞信号 3.1 信号其他相关常见概念 3.2 信号在内核中的表示 3.3 sigset_t 3.4 信号集操作函数 3.5 sigprocmask函数 3.6 sigpending函数 3.7 信号集实验 四、深入理解捕捉信号 4.1 进程地址空间二次理解&#xff08;内核空间与用户空间&#xff09; 4.2 用…

黑马的redis实战篇-短信登录

目录 四、实战篇-短信登录 4.1 导入黑马点评项目 1、后端&#xff1a; 2、前端 4.2 基于Session实现登录 1、发送验证码 2、短信验证码登录注册 3、校验登录状态 4.3 集群的session共享问题 4.4 基于Redis实现共享session登录 1、发送验证码 2、短信验证码登录注册 …

NumPy 秘籍中文第二版:六、特殊数组和通用函数

原文&#xff1a;NumPy Cookbook - Second Edition 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 在本章中&#xff0c;我们将介绍以下秘籍&#xff1a; 创建通用函数查找勾股三元组用chararray执行字符串操作创建一个遮罩数组忽略负值和极值使用recarray函数创建一…

蓝桥杯之单片机学习(终)——关于之前文章的错误及更正(附:第十四届蓝桥杯单片机赛题)

文章目录零、吐槽一、关于自创模板&#xff0c;和自写模板库的问题二、关于 详解A/D、D/A、PCF8591 这篇文章一些小错误三、模板最终版本main.cds1302,hds1302.conewire.honewire.ciic.hiic.c附、第十四届蓝桥杯单片机赛题零、吐槽 今年是矩阵键盘三个协议一起调用啊。真是一年…

“AI+机器人”持续为多领域增“智”添“质”,开启效益增长飞轮

近期&#xff0c;工信部等17部门联合推出《“机器人”应用行动实施方案》&#xff0c;全面加快机器人领域应用拓展。据方案提出&#xff0c;至2025年&#xff0c;制造业机器人密度较2020年将实现翻番&#xff0c;服务机器人及特种机器人行业应用深度与广度显著提升。机器人融合…

服务器被DDoS攻击,怎么破?

文章目录前言网站受到DDoS的症状判断是否被攻击查看网络带宽占用查看网络连接TCP连接攻击SYN洪水攻击防御措施TCP/IP内核参数优化iptables 防火墙预防防止同步包洪水&#xff08;Sync Flood&#xff09;Ping洪水攻击&#xff08;Ping of Death&#xff09;控制单个IP的最大并发…

基于SpringBoot的私人健身和教练的预约管理系统源码数据库论文

目 录 第一章 概述 1.1研究背景 1.2开发意义 1.3研究现状 1.4研究内容 1.5论文结构 第二章 开发技术介绍 2.1系统开发平台 2.2平台开发相关技术 2.2.1 Javar技术 2.2.2 Mysql数据库介绍 2.2.3 Mysql环境配置 2.2.4 B/S架构 2.2.5 Springboot框架 …

主动配电网故障恢复的重构与孤岛划分统一模型研究【升级版本】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

V8引擎执行原理

v8是C编写的Google开源高性能JavaScript和WebAssembly引擎&#xff0c;它用于Chrome和Node.js等。 它实现ECMAScript和WebAssembly。 v8可独立运行&#xff0c;也可嵌入到任何C应用程序中。 parse模块 parse模块会将JavaScript代码转换成AST(抽象语法树)&#xff0c;因为解…

[LeetCode周赛复盘] 第 340 场周赛20230409

[LeetCode周赛复盘] 第 340 场周赛20230409 一、本周周赛总结二、 6361. 对角线上的质数1. 题目描述2. 思路分析3. 代码实现三、6360. 等值距离和1. 题目描述2. 思路分析3. 代码实现四、6359. 最小化数对的最大差值1. 题目描述2. 思路分析3. 代码实现五、 6353. 网格图中最少访…

【排序】排序这样写才对Ⅰ --插入排序与选择排序

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…

Axios请求(对于ajax的二次封装)——Axios请求的响应结构、默认配置

Axios请求&#xff08;对于ajax的二次封装&#xff09;——Axios请求的响应结构、默认配置知识回调&#xff08;不懂就看这儿&#xff01;&#xff09;场景复现核心干货axios请求的响应结构响应格式详解实际请求中的响应格式axios请求的默认配置全局axios默认值&#xff08;了解…

Debug | wget 的安装与使用(Windows)

!wget -nc http://labfile.oss.aliyuncs.com/courses/780/WeatherData.zip 报错信息&#xff1a; wget 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 分析&#xff1a; 在jupyter notebook中做机器学习时导入数据使用!wget遇到了这个问题&#xff0c;查到…

轻松上手git代码版本管理工具--协同开发-冲突解决、线上分支合并以及使用pycharm操作git

一、协同开发 多人合作开发一个项目---->多人公用一个远程仓库 以后台项目为例: git init # git管理设置忽略文件.gitignore git add .git commit -m 第一次提交,写完了首页功能远程新建一个远程仓库(空) 创建一个origin git remote add origin git@gitee.com:xx…

穿戴规范智能识别系统 yolov7

穿戴规范智能识别系统通过yolov7python网络模型AI深度视觉学习算法&#xff0c;穿戴规范智能识别系统对工厂画面中人员穿戴行为自动识别分析&#xff0c;发现现场人员未按照规定穿戴着装&#xff0c;立即抓拍告警。YOLOv7 的发展方向与当前主流的实时目标检测器不同&#xff0c…

垃圾满溢检测系统 yolov5

垃圾满溢检测系统通过pythonyolov5网络模型技术&#xff0c;垃圾满溢检测系统对控画面中小区内的垃圾桶进行7*24小时不间断监控&#xff0c;发现垃圾桶溢满周围有堆积物立即触发预警推送给相关人员处理。YOLOv5中在训练模型阶段仍然使用了Mosaic数据增强方法&#xff0c;该算法…

kubeadm方式部署k8s最新版本V1.26.2

Kubernetes核心概念 Master主要负责资源调度&#xff0c;控制副本&#xff0c;和提供统一访问集群的入口。--核心节点也是管理节点 Node是Kubernetes集群架构中运行Pod的服务节点。Node是Kubernetes集群操作的单元&#xff0c;用来承载被分配Pod的运行&#xff0c;是Pod运行的宿…

测试7年,去过阿里也去过小公司,给你们年轻人一个忠告...

你眼中的软件测试岗位是怎样的&#xff1f;大部分人可能会给出这样的回答&#xff1a;“测试&#xff1f;简单啊&#xff0c;没什么技术含量&#xff0c;无非就是看需求、看业务手册、看设计文档、然后点点功能是否实现&#xff0c;麻烦点的就是测试下部署安装是否出现兼容性问…