秒懂Linux之管道通信

news2024/12/26 23:53:41

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

 

前言

进程间通信目的

管道通信

原理

匿名管道

测试样例

情况与特点

模拟进程池

命名管道

全部代码


前言

两个进程之间可以进行数据的直接传递吗?——不可以,进程必须得具备独立性。

进程间通信目的

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

管道通信

原理

让不同进程看到同一份资源有很多种做法,现在我们来学习其中的一种:管道通信

我们让进程以写的方式打开文件后再以读的方式打开文件,虽然是打开同一个文件,但在文件描述符中是相当于2个不同的文件对象的~不过二者指向同一处内存,因为代码和数据是一起的~
当我们创建子进程时会拷贝父进程相关的数据与代码,这里面就包括文件描述符~但并不会拷贝其打开的文件,所以最终子进程会和父进程共同指向文件~相当于浅拷贝~
这种让不同进程看到同一份资源的方式叫做管道通信,很好利用了父子进程间关系的一种通信方式,而接下来我们就可以实现进程间的数据传输~例如:父进程只负责读取文件(关闭父进程写通道),子进程负责写入文件(关闭子进程读通道)~
ps:struct file是允许有多个指针指向的,它采用的是引用计数的方式,只有自减为0才会关闭文件

匿名管道

利用函数pipe可以做到匿名管道的效果:不再需要把数据从磁盘中加载进入内容,不需要向磁盘中刷新数据,且磁盘中并不存在的文件,它是属于内存级别的文件~

测试样例

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
//写函数
void writer(int wfd)
{
    const char *str = "hello father, I am child";
    char buffer[128];
    //记录写几次
    int cnt = 0;
    pid_t pid = getpid();
    while(1)
    {
        //把字符串都写入到buffer中
        snprintf(buffer, sizeof(buffer), "message: %s, pid: %d, count: %d\n", str, pid, cnt);
        //把buffer的内容写到文件描述符为4的位置
        write(wfd, buffer, strlen(buffer));
        cnt++;
        sleep(1);
    }
}
void reader(int rfd)
{
    char buffer[1024];
    while(1)
    {
        //从文件描述符为3的位置读取内容
        ssize_t n = read(rfd, buffer, sizeof(buffer)-1);
        (void)n;
        printf("father get a message: %s", buffer);
    }
}


int main()
{
    // 1. 
    int pipefd[2];
    int n = pipe(pipefd);
    if(n < 0) return 1;
    printf("pipefd[0]: %d, pipefd[1]: %d\n", pipefd[0]/*read*/, pipefd[1]/*write*/); // 3, 4

    // 2. 
    pid_t id = fork();
    if(id == 0)
    {
        //child: w 子进程负责写
        close(pipefd[0]);

        writer(pipefd[1]);

        exit(0);
    }

    // father: r 父进程负责读
    close(pipefd[1]);

    reader(pipefd[0]);
    wait(NULL);

    return 0;
}

情况与特点

//写函数
void writer(int wfd)
{
    const char *str = "hello father, I am child";
    char buffer[128];
    //记录写几次
    int cnt = 0;
    pid_t pid = getpid();
    while(1)
    {
        //在不关闭写端fd的情况下让子进程休眠10s再写
        sleep(10);
        //把字符串都写入到buffer中
        snprintf(buffer, sizeof(buffer), "message: %s, pid: %d, count: %d\n", str, pid, cnt);
        //把buffer的内容写到文件描述符为4的位置
        write(wfd, buffer, strlen(buffer));
        cnt++;
    }
}

管道内部没有数据且子进程并没有关闭写端fd的情况下,父进程(读端)会一直阻塞等待,直到pipe有数据。——父进程作为读端会一直读取子进程写端写入的数据,没写则一直等待~

void reader(int rfd)
{
    char buffer[1024];
    while(1)
    {
        //在不关闭读端fd的情况下让父进程休眠100s再读
        sleep(100);
        //从文件描述符为3的位置读取内容
        ssize_t n = read(rfd, buffer, sizeof(buffer)-1);
        (void)n;
        printf("father get a message: %s", buffer);
    }
}

管道内部数据被写满且父进程并没有关闭读端fd的情况下,子进程(写端)写满后就会阻塞等待。——子进程作为写端是无法一直写入数据的,管道空间有限~

//写函数
void writer(int wfd)
{
    const char *str = "hello father, I am child";
    char buffer[128];
    //记录写几次
    int cnt = 0;
    pid_t pid = getpid();
    while(1)
    {
        /* //在不关闭写端fd的情况下让子进程休眠10s再写
        sleep(10); */
        /* //把字符串都写入到buffer中
        snprintf(buffer, sizeof(buffer), "message: %s, pid: %d, count: %d\n", str, pid, cnt);
        //把buffer的内容写到文件描述符为4的位置
        write(wfd, buffer, strlen(buffer)); */

        char c = 'A';
        write(wfd,&c,1);
        cnt++;
        //写入一些数据后就退出
        printf("cnt: %d\n", cnt);
        if(cnt==10) break;
    }
    //关闭子进程写端
    close(wfd);
}
void reader(int rfd)
{
    char buffer[1024];
    while(1)
    {
        /* //在不关闭读端fd的情况下让父进程休眠100s再读
        sleep(100); */


        sleep(1);
        //从文件描述符为3的位置读取内容
        ssize_t n = read(rfd, buffer, sizeof(buffer)-1);
        if(n>0)
        {
            printf("father get a message: %s, n:%ld\n", buffer,n);
        }
        else if(n == 0)
        {
            printf("read pipe done, read file done!\n");
            break;
        }
        else
            break;
    }
}

当写端写入数据后突然不写了并且关闭写端fd(防止再次写入数据)时,读端会将管道内的数据读完,最后会读到read的返回值为0,这也代表读到了数据的末尾。把数据读完了——写端不再写入数据时读端会获取到read函数的返回值,当获取到0时也意味着读取结束了。

//写函数
void writer(int wfd)
{
    const char *str = "hello father, I am child";
    char buffer[128];
    //记录写几次
    int cnt = 0;
    pid_t pid = getpid();
    while(1)
    {
        /* //在不关闭写端fd的情况下让子进程休眠10s再写
        sleep(10); */
        /* //把字符串都写入到buffer中
        snprintf(buffer, sizeof(buffer), "message: %s, pid: %d, count: %d\n", str, pid, cnt);
        //把buffer的内容写到文件描述符为4的位置
        write(wfd, buffer, strlen(buffer)); */
        sleep(1);
        char c = 'A';
        write(wfd,&c,1);
        cnt++;
        //写入一些数据后就退出
        printf("cnt: %d\n", cnt);
        //if(cnt==10) break;
    }
    //关闭子进程写端
    //close(wfd);
}
void reader(int rfd)
{
    char buffer[1024];
    int cnt = 10;
    while(1)
    {
        /* //在不关闭读端fd的情况下让父进程休眠100s再读
        sleep(100); */


        //从文件描述符为3的位置读取内容
        ssize_t n = read(rfd, buffer, sizeof(buffer)-1);
        if(n>0)
        {
            printf("father get a message: %s, n:%ld\n", buffer,n);
        }
        else if(n == 0)
        {
            printf("read pipe done, read file done!\n");
            break;
        }
        else
            break;
        cnt--;
        if(cnt == 0) break;
    }
    close(rfd);
    printf("read endpoint close!\n");
}

当读端不再读取数据且关闭了读端fd时,OS就会直接终止写入的进程(子进程),通过信号13(SIGPIPE)杀死进程~

以上情况分别呈现了5种特性:

  • 自带同步机制
  • 血缘关系进程进行通信,常见父与子
  • pipe是面向字节流的
  • 父子退出,管道自动释放,文件的生命周期是随进程的
  • 管道只能单向通信

模拟进程池

当我们想让更多的进程与进程之间进行分工合作时,我们可以利用父子进程这种特殊的管道关系来构建一个进程池以达到分工的目的~

把整体框架搭好~
#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include <unistd.h>
#include <ctime>

using namespace std;

enum
{
    UsageError = 1,
    ArgError,
    PipeError
};

//提示正确的命令行输入格式
void Usage(const std::string&s)
{
    cout<<"Usage:"<<s<<"subprocess-num"<<endl;
}
//假设我们构建进程池中有5个管道~
// ./processpool 5
int main(int argc, char* argv[])
{
    //如果写入的命令行参数个数不到2个
    if(argc!=2)
    {
        Usage(argv[0]);
        return UsageError;
    }
    //获取管道个数
    int sub_process_num = std::stoi(argv[1]);
    //确保为多管道
    if(sub_process_num<=0) return 1;
    //1.遍历创建通信管道与子进程
    for(int i = 0;i<sub_process_num;i++)
    {
        //设置管道
        int pipefd[2]{0};
        //获取返回值
        int n = pipe(pipefd);
        if(n<0) return PipeError;
        //创建子进程
        pid_t id = fork();
        if(id==0)
        {
            //child
            //子进程作为写端,关闭读端
            close(pipefd[1]);

            exit(0);
        }
        //father 父进程作为读端,关闭写端
        close(pipefd[0]);
    }
    //2.控制子进程完成任务

    //3.回收子进程

    return 0;
}

目前我们要解决的就是每次在创建子进程时管道的独立性问题,要想管理多个通信管道——先描述,再组织~

using namespace std;

enum
{
    UsageError = 1,
    ArgError,
    PipeError
};

//描述通信管道的类
class channel
{
    public:
    //构造函数
    channel(int wfd,pid_t sub_process_id,string name)
    :_wfd(wfd),_sub_process_id(sub_process_id),_name(name)
    {}

    //打印管道信息
    void PrintDebug()
    {
        cout<<"_wfd: "<<_wfd;
        cout<<"_sub_process_id: "<<_sub_process_id;
        cout<<"_name: "<<_name<<endl;
    }

    ~channel()
    {

    }

    private:
    int _wfd;  //子进程的端口
    pid_t _sub_process_id;//所属子进程的pid
    string _name; //管道独立名字

}

//提示正确的命令行输入格式
void Usage(const std::string&s)
{
    cout<<"Usage:"<<s<<"subprocess-num"<<endl;
}
//假设我们构建进程池中有5个管道~
// ./processpool 5
int main(int argc, char* argv[])
{
    //如果写入的命令行参数个数不到2个
    if(argc!=2)
    {
        Usage(argv[0]);
        return UsageError;
    }
    //获取管道个数
    int sub_process_num = std::stoi(argv[1]);
    //确保为多管道
    if(sub_process_num<=0) return 1;

    //组织管道
    vector<channel> vc;

    //1.遍历创建通信管道与子进程
    for(int i = 0;i<sub_process_num;i++)
    {
        //设置管道
        int pipefd[2]{0};
        //获取返回值
        int n = pipe(pipefd);
        if(n<0) return PipeError;
        //创建子进程
        pid_t id = fork();
        if(id==0)
        {
            //child
            //子进程作为写端,关闭读端
            close(pipefd[1]);

            exit(0);
        }
        //记录该管道
        string cname = "channel"+to_string(i);
        //把管道组织起来,以匿名对象作插入值
        vc.push_back(channel(pipefd[1],id,cname));

        //father 父进程作为读端,关闭写端
        close(pipefd[0]);
        //整理完毕后添加该管道信息
    }
    //2.控制子进程完成任务

    //3.回收子进程

    return 0;
}

组织完毕,接下来就可以开始控制子进程了~

//2.控制子进程完成任务
    for(auto& e:vc)
    {
        e.PrintDebug();
    }

把这些代码包装成C++的方式~
//描述进程池的类,管理进程池
class ProcessPool
{
public:
    ProcessPool(int sub_process_num)
    : _sub_process_num(sub_process_num)
    {
    }
    int CreateProcess() 
    {
        for(int i = 0;i<_sub_process_num;i++)
        {
            //设置管道
            int pipefd[2]{0};
            //获取返回值
            int n = pipe(pipefd);
            if(n<0) return PipeError;
            //创建子进程
            pid_t id = fork();
            if(id==0)
            {
                //child
                //子进程作为写端,关闭读端
                close(pipefd[1]);
                sleep(3);
                exit(0);
            }
            //记录该管道
            string cname = "channel"+to_string(i);

            //father 父进程作为读端,关闭写端
            close(pipefd[0]);
            //整理完毕后添加该管道信息,以匿名对象作插入值
            vc.push_back(channel(pipefd[1],id,cname));
        }
        return 0;
    }
    
    
    void Debug()
    {
        for (auto &e : vc)
        {
            e.PrintDebug();
        }
    }
    ~ProcessPool()
    {
    }

private:
    int _sub_process_num;//进程池中管道的个数
    vector<channel> vc;//组织管道
};
int main(int argc, char* argv[])
{
    //如果写入的命令行参数个数不到2个
    if(argc!=2)
    {
        Usage(argv[0]);
        return UsageError;
    }
    //获取管道个数
    int sub_process_num = std::stoi(argv[1]);
    //确保为多管道
    if(sub_process_num<=0) return 1;

    

    //1.创建通信管道与子进程
    //用指针去调用成员函数
    ProcessPool * processpool_ptr = new ProcessPool(sub_process_num);
    processpool_ptr->CreateProcess();
    processpool_ptr->Debug(); 

    //对象直接调用
    /* ProcessPool pp(5);
    pp.CreateProcess();
    pp.Debug(); */
    //2.控制子进程完成任务
   
    //3.回收子进程

    return 0;
}

难点来了,利用回调函数开始分配任务
#pragma once

#include <iostream>
#include <unistd.h>

using namespace std;

typedef void(*work_t)();  //函数指针类型

void worker()
{
    while(1)
    {
        /* uint32_t command_code = 0;
        ssize_t n = read(0, &command_code, sizeof(command_code));
        if(n == sizeof(command_code))
        {
            if(command_code >= 3) continue;
            tasks[command_code]();
        } */
        cout << "I am worker: " << getpid() << endl;
        sleep(1);
    }
}

现在才是真正完成了创建进程与管道的任务并且我们还可以给子进程指派任务~

第二阶段:控制子进程
我们之所以重定向就是为了类似下面的效果
#pragma once

#include <iostream>
#include <unistd.h>

using namespace std;

typedef void(*work_t)();  //函数指针类型
typedef void(*task_t)();  //函数指针类型

void PrintLog()
{
    cout << "printf log task" << endl;
}

void ReloadConf()
{
    cout << "reload conf task" << endl;
}

void ConnectMysql()
{
    cout << "connect mysql task" << endl;
}
//一共有3个任务
task_t tasks[3] = {PrintLog, ReloadConf, ConnectMysql};

//随机选择任务
uint32_t NextTask()
{
    return rand() % 3;
}
void worker()
{
    //从标准输入0中读取任务,将来想要执行某个任务只需要向管道发送特定的0,1,2数字即可
    while(1)
    {
        //读任务的命名码,0就是前面任务的下标
        uint32_t command_code = 0;
        //从标准输入0中读取任务码,这样子就不用特意把管道文件描述符传递过来了,实际上还是读到了管道的数据
        ssize_t n = read(0, &command_code, sizeof(command_code));
        if(n == sizeof(command_code))//读进来的数据正确
        {
            if(command_code >= 3) continue;
            tasks[command_code]();//执行对应任务
        } 
        cout << "I am worker: " << getpid() << endl;
        sleep(1);
    }
}
#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include <unistd.h>
#include <ctime>

using namespace std;
#include "task.hpp"
enum
{
    UsageError = 1,
    ArgError,
    PipeError
};

//描述通信管道的类
class channel
{
    public:
    //构造函数
    channel(int wfd,pid_t sub_process_id,string name)
    :_wfd(wfd),_sub_process_id(sub_process_id),_name(name)
    {}

    //打印管道信息
    void PrintDebug()
    {
        cout<<"_wfd: "<<_wfd;
        cout<<"_sub_process_id: "<<_sub_process_id;
        cout<<"_name: "<<_name<<endl;
    }

    string name() {return _name;}
    int wfd() {return _wfd;}
    pid_t pid() { return _sub_process_id; }

    ~channel()
    {

    }

    private:
    int _wfd;  //子进程的端口
    pid_t _sub_process_id;//所属子进程的pid
    string _name; //管道独立名字

};





//描述进程池的类,管理进程池
class ProcessPool
{
public:
    ProcessPool(int sub_process_num)
    : _sub_process_num(sub_process_num)
    {
    }
    int CreateProcess(work_t work)//回调函数 
    {
        for(int i = 0;i<_sub_process_num;i++)
        {
            //设置管道
            int pipefd[2]{0};
            //获取返回值
            int n = pipe(pipefd);
            if(n<0) return PipeError;
            //创建子进程
            pid_t id = fork();
            if(id==0)
            {
                //child
                //子进程作为读端,关闭写端
                close(pipefd[1]);
                //子进程开始执行任务
                //子进程从标准输入0当中读数据而非管道,但在键盘中读入的数据还是管道的
                dup2(pipefd[0],0);
                work();
                exit(0);
            }
            //记录该管道
            string cname = "channel-"+to_string(i);

            //father 父进程作为写端,关闭读端
            close(pipefd[0]);
            //整理完毕后添加该管道信息,以匿名对象作插入值
            vc.push_back(channel(pipefd[1],id,cname));
        }
        return 0;
    }
    //选择任务时还得保证任务能负载均衡地出现在各个管道中
    int NextChannel()
    {
        static int next = 0;
        int c = next;
        next++;
        next %= vc.size();
        return c;
    }
    //发送任务
     void SendTaskCode(int index, uint32_t code)
    {
        cout << "send code: " << code << " to " << vc[index].name() << " sub prorcess id: " <<  vc[index].pid() << endl;
        //向特定管道的写端写入任务码code
        write(vc[index].wfd(), &code, sizeof(code));
    }
    void Debug()
    {
        for (auto &e : vc)
        {
            e.PrintDebug();
        }
    }
    ~ProcessPool()
    {
    }

private:
    int _sub_process_num;//进程池中管道的个数
    vector<channel> vc;//组织管道
};







//提示正确的命令行输入格式
void Usage(const std::string&s)
{
    cout<<"Usage:"<<s<<"subprocess-num"<<endl;
}
//假设我们构建进程池中有5个管道~
// ./processpool 5
int main(int argc, char* argv[])
{
    //如果写入的命令行参数个数不到2个
    if(argc!=2)
    {
        Usage(argv[0]);
        return UsageError;
    }
    //获取管道个数
    int sub_process_num = std::stoi(argv[1]);
    //确保为多管道
    if(sub_process_num<=0) return ArgError;

    //随机数
    srand((uint64_t)time(nullptr));
    //1.创建通信管道与子进程
    //用指针去调用成员函数
    ProcessPool * processpool_ptr = new ProcessPool(sub_process_num);
    processpool_ptr->CreateProcess(worker);
    //processpool_ptr->Debug(); 

    //对象直接调用
    /* ProcessPool pp(5);
    pp.CreateProcess();
    pp.Debug(); */
    // 2. 控制子进程
    while(true)
    {
        // a. 选择一个进程和通道
        int channel = processpool_ptr->NextChannel();
        // cout << channel.name() << endl;

        // b. 你要选择一个任务
        //选择任务时还得保证任务能负载均衡地出现在各个管道中
        uint32_t code = NextTask();

        // c. 选择通道+任务最终发送任务
        processpool_ptr->SendTaskCode(channel, code);

        sleep(1);
    }
    //3.回收子进程
    delete processpool_ptr;
    return 0;
}

以上是分配任务和选择管道让子进程执行工作~
再优化一下代码结构:

最后再测试一下~ 

出现了一个奇怪的问题:为什么每个子进程的管道读端都是3?我们来画图分析一下~

 关闭父进程读端与子进程写端:

问题就出在这里,当再创建子进程和通道时父进程原本关闭的3号读端重新作为了新管道的读端,至于写端则继续往下寻找(5号写端)子进程每一次的创建都会拷贝父进程的文件描述符,所以新的子进程3号仍是读端~这样重复下去父进程每个管道的写端都依次递增,而子进程的读端永远都是3号~

还有一个问题就是连原本父进程指向第一个管道的读端都给拷贝过来了, 这样会引发一个问题,当父进程想要关闭写端口时实际上只是引用计数--罢了,并没有真正关闭写端口~

所以子进程读端口都为3只是该进程池的特性,而子进程写端口过多才是我们需要关注并解决的问题~

搞定完这个bug后我们开始来写进程池的最后一步:回收子进程~

回收子进程有两个问题

  • 回收子进程之前如何让子进程全部退出?子进程在worker函数里会一直阻塞在read中~
  • 我们让read返回值为0即可——意味着写端不写且写端口关闭,读到0代表读取结束~
  • 怎么让所有已经退出的子进程对他进行资源回收

  • wait/waitpid

//把所有管道的写端口关闭即可让所有子进程退出
    void QuitAll()
    {
        for(auto &channel: vc)
        {
            channel.Close();
            //关闭完顺便回收子进程
            pid_t pid = channel.pid();

            pid_t rid = waitpid(pid,nullptr,0);
            if(rid==pid)
            {
                std::cout << "wait sub process: " << pid << " success..." << std::endl;
            }
            std::cout << channel.name() << " close done" << " sub process quit now : " << channel.pid() << std::endl;
        }
        
    }

完结撒花~

命名管道

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

在图中我们可以获取到以下几点信息:

当进程A以读方式打开fifo文件,进程B以写方式打开fifo文件时各自的struct file是独立的~对于进程B而言OS并不会再为其打开的文件创建新的inode、缓冲区等资源,而是和进程A先打开的一起共享资源~

接下来我们就来创建命名管道

命名管道的特性其实与匿名管道基本一致,唯一不同的就是它是让两个不同进程(无血缘关系)进行通信~

#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

using namespace std;

#define Mode 0666
#define Path "./fifo"

class Fifo
{
    public:
        Fifo(const string& path)
        :_path(path)
        {
            umask(0);
            //创建管道文件
            int n = mkfifo(_path.c_str(),Mode);
            if(n==0)
            {
                cout<<"mkfifo success"<<endl;
            }
            else
            {
                cerr << "mkfifo failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
            }
        }
       ~Fifo()
    {
        int n = unlink(_path.c_str());
        if (n == 0)
        {
            cout << "remove fifo file " << _path << " success" << endl;
        }
        else
        {
            cerr << "remove failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
        }
    }
    private:
        string _path; //文件路径+文件名
};

#endif
#include "comm.hpp"


/* int main()
{
    
    cout<<"hello i am client"<<endl;
    return 0;
} */

int main()
{
    //以写方式打开命名管道(特殊文件)并记录其写端口号
    int wfd = open(Path, O_WRONLY);
    if (wfd < 0)
    {
        cerr << "open failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
        return 1;
    }
    
    string inbuffer;
    while (true)
    {
        cout << "Please Enter Your Message# ";
        //从键盘上获取内容输入到inbuffer中
        std::getline(cin, inbuffer);
        //人工输入退出
        if(inbuffer == "quit") break;
        //开始在对应文件描述符中的文件写入数据
        ssize_t n = write(wfd, inbuffer.c_str(), inbuffer.size());
        if (n < 0)
        {
            cerr << "write failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
            break;
        }
    }

    close(wfd);
    return 0;
}
#include "comm.hpp"
#include <unistd.h>
/* int main()
{
    Fifo fifo("./fifo");
    cout<<"hello i am server"<<endl;
    return 0;
} */


int main()
{
    //创建命名管道
    Fifo fifo(Path);
    //以读方式打开命名管道(特殊文件)
    int rfd = open(Path, O_RDONLY);
    if (rfd < 0)
    {
        cerr << "open failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
        return 1;
    }
    // 如果我们的写端没打开,先读打开,open的时候就会阻塞,直到把写端打开,读open才会返回
    cout << "open success" << endl;

    char buffer[1024];
    while (true)
    {
        //开始到对应文件描述符的文件进行数据读取
        ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n] = 0;
            cout << "client say : " << buffer << endl;
        }
        //写端不写且关闭,则读端读取完后也要离开并关闭-
        else if (n == 0)
        {
            cout << "client quit, me too!!" << endl;
            break;
        }
        else
        {
            cerr << "read failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
            break;
        }
    }

    close(rfd);
    return 0;
}

全部代码

模拟进程池 · d883fa6 · 玛丽亚后/keep - Gitee.com

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

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

相关文章

odoo14 | 报错:Database backup error: Access Denied

这两天抽空想为自己快速做一个简单的管理系统&#xff0c;来信息化管理一下自己家里的一些菜谱、电视剧下载清单等事情&#xff0c;我又不想大动干戈的用Java写管理系统&#xff0c;我就想用已经手生了两年半的odoo快速搭一个系统用用得了&#xff0c;结果还遇上了这么个事 根…

Java设计模式—面向对象设计原则(四) ----->接口隔离原则ISP (完整详解,附有代码+案例)

文章目录 3.4 接口隔离原则(ISP)3.4.1 概述3.4.2 案列 3.4 接口隔离原则(ISP) Interface Segregation Principle&#xff0c;简称ISP 3.4.1 概述 客户端测试类不应该被迫依赖于它不使用的方法&#xff1b;一个类对另一个类的依赖应该建立在最小的接口上。 3.4.2 案列 面看…

PMP--一模--解题--21-30

文章目录 9.资源管理21、 [单选] 项目经理发现一个不可预料的高影响风险已经成为项目的一个因素&#xff0c;团队成员之间的自身利益导致问题得不到解决&#xff0c;项目经理必须快速行动&#xff0c;让团队重新集中精力&#xff0c;以便项目恢复进度&#xff0c;项目经理应该使…

通信工程学习:什么是LCAS链路容量调整机制

LCAS&#xff1a;链路容量调整机制 LCAS&#xff08;Link Capacity Adjustment Scheme&#xff09;链路容量调整机制是一种在ITU-T G.7042中定义的技术&#xff0c;旨在解决传统SDH&#xff08;同步数字体系&#xff09;网络在传输数据业务时带宽分配不灵活的问题。以下是LCAS链…

【 C++ 】C/C++内存管理

前言&#xff1a; &#x1f618;我的主页&#xff1a;OMGmyhair-CSDN博客 目录 一、C/C内存分布 二、C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free malloc&#xff1a; calloc&#xff1a; realloc&#xff1a; free&#xff1a; 三、C内存管理方式…

用Mapmost聚类图分析世界

聚类地图是一种数据可视化工具&#xff0c;能够帮助用户在地图上直观地显示大量地理数据点。当数据点过多时&#xff0c;单独显示每个点会使地图变得混乱&#xff0c;而聚类地图通过将相近的数据点聚集在一起&#xff0c;减少了视觉复杂性&#xff0c;便于分析和理解。聚类地图…

在Linux上安装中创中间件InforSuiteAS(二进制文件安装)

在Linux上安装中创中间件InforSuiteAS&#xff08;二进制文件安装&#xff09; 前言1、环境准备1.1 支持的操作系统1.2 依赖软件 2、安装步骤2.1 下载并解压安装包2.2 执行安装2.3 修改防火墙设置2.4 启动InforSuiteAS2.5 InforSuiteAS常用命令2.6 验证安装 3、常见问题及解决方…

【Petri网导论学习笔记】Petri网导论入门学习(三)

Petri网导论入门学习&#xff08;三&#xff09; Petri 网导论学习笔记&#xff08;三&#xff09;定义 1.4定义 1.5定义 1.6定义 1.7 Petri 网导论学习笔记&#xff08;三&#xff09; 如需学习转载请注明原作者并附本帖链接&#xff01;&#xff01;&#xff01; 如需学习转载…

Axure设计之全屏与退出全屏交互实现

在Axure RP中&#xff0c;设计全屏与退出全屏的交互功能可以极大地提升用户体验&#xff0c;尤其是在展示产品原型或进行演示时。本文将详细介绍如何在Axure RP中通过结合JavaScript代码实现全屏与退出全屏的交互效果。 ​ Axure原型设计web端交互元件库&#xff1a;https://…

网络安全产品认证证书大全(持续更新...)

文章目录 一、引言二、《计算机信息系统安全专用产品销售许可证》2.1 背景2.2 法律法规依据2.3 检测机构2.4 检测依据2.5 认证流程2.6 证书样本 三、《网络关键设备和网络安全专用产品安全认证证书》3.1 背景3.2 法律法规依据3.3 检测机构3.4安全认证和安全检测依据标准3.5 认证…

9月→2024年计算机与信息安全国际会议

【9月→郑州、吉隆坡双会场】 Springer-LNICST &#x1f525;&#x1f525;2024年计算机与信息安全国际会议&#xff08;WCCIS 2024&#xff09; 会议时间&#xff1a;2024年9月20-22日 论文收录&#xff1a;EI&#xff0c;Scopus稳定检索 网络安全&#xff0c;访问控制&am…

了解MySQL 高可用架构:主从备份

为了防止数据库的突然挂机&#xff0c;我们需要对数据库进行高可用架构。主从备份是常见的场景&#xff0c;通常情况下都是“一主一从/(多从)”。正常情况下&#xff0c;都是主机进行工作&#xff0c;从机进行备份主机数据&#xff0c;如果主机某天突然意外宕机&#xff0c;从机…

Android 13 固定systemUI的状态栏为黑底白字,不能被系统应用或者三方应用修改

目录 一.背景 二.思路 三.代码流程 1.colos.xml自定义颜色 2.设置状态栏的背景颜色 3.对View进行操作 ①.对Clock(状态栏左侧的数字时钟)进行操作 ②.对电池(BatteryMeterView)进行操作 4.锁屏状态栏 5.patch汇总 一.背景 客户需求将状态栏固定成黑底白字,并且不能让系…

红外小目标检测:基于深度学习

目录 ​编辑 1.红外成像技术的优势 2.红外小目标检测的基本原理 常用方法 1. 背景抑制法 2. 基于滤波的方法 3. 基于模型的方法 4. 基于深度学习的方法 5. 多传感器融合方法 3.代码实战 案例背景 数据准备 模型选择 代码实现 讲解 4.应用场景 5.未来发展趋势 …

NISP 一级 | 4.2 操作系统的安全威胁

关注这个证书的其他相关笔记&#xff1a;NISP 一级 —— 考证笔记合集-CSDN博客 0x01&#xff1a;漏洞和漏洞扫描 对计算机系统安全威胁最大的就是系统本身的漏洞&#xff0c;只有存在漏洞&#xff0c;黑客才有机会入侵我们的计算机系统。具统计证明&#xff0c;99% 的黑客攻击…

基于spring实现博客项目的删除和更新(五)

8. 实现用户退出 前端直接清除掉token即可. 实现客⼾端代码 <注销>链接已经提前添加了onclick事件 &#xff0c;在common.js中完善logout⽅法 function logout(){localStorage.removeItem("user_token");location.href "blog_login.html"; } 点击…

Adobe 将推出人工智能视频模型 Firefly 视频模型: 最长 5 秒,支持视频编辑

最近&#xff0c;Adobe 发布了一款全新的创意工具–Adobe Firefly 视频模型。 这一创新工具标志着 Adobe 在现有 Firefly 生成式人工智能图像模型的基础上&#xff0c;大胆涉足人工智能生成视频领域。 Adobe 表示&#xff0c;该模型是经过道德训练的&#xff0c;使用的数据都是…

数字孪生之-3D可视化

定义&#xff1a; 广义&#xff1a;一切现实物体的虚拟化表达&#xff0c;都可以算是广义的数字孪生行业&#xff1a;数字孪生体应该是与现实物体一对一映射、实时数据连接、有数据模型和对应的数据的 个人理解数字孪生的实现还是基于数据驱动&#xff0c;加上上帝视角&#xf…

微软九月补丁星期二发现了 79 个漏洞

微软将在2024 年 9 月补丁星期二修复 79 个漏洞。 微软有证据表明&#xff0c;发布的四个漏洞被野外利用和/或公开披露&#xff1b;所有四个漏洞均已在CISA KEV上列出。微软还在修补四个关键的远程代码执行 (RCE) 漏洞。 不同寻常的是&#xff0c;微软本月尚未修补任何浏览器…

Leetcode面试经典150题-141.环形链表

题目比较简单&#xff0c;重点是理解思想 解法都在代码里&#xff0c;不懂就留言或者私信 /*** Definition for singly-linked list.* class ListNode {* int val;* ListNode next;* ListNode(int x) {* val x;* next null;* }* }*/ public…