Linux IPC-管道

news2024/12/23 17:37:38

前言

前面我们已经对进程概念、进程控制做了介绍!本期开始将来介绍进程的最后一章进程间通信,即如何让两个进程进行通信!本博客主要介绍管道!

本期内容介绍

• 初识进程间通信

• 管道

一、初识进程间通信

1、进程间通信的概念

进程间通信(Inter-Process Communication,简称IPC是指在不同进程之间传输数据或信号的技术!

2、进程间通信的目的

进程之间也是需要某种协同的,但是进行协同的前提是得双方进行通信。通信是要传递数据的,根据数据的类别,可以将通信目的分为:

• 数据传输 :一个进程需要将自己的数据发送给另一个进程;

• 资源共享 :多进程之间共享同样的资源

• 通知事件 :一个进程需要给另一个进程发送消息,通知他(他们)发生了某种事件(例如,进程终止要统治父进程)

• 进程控制 :有些进程希望完全控制另一个进程执行(入Debug进程),此时控制进程希望能够拦截另一个进程的所有的陷入和异常,并能够及时知道他的状态改变!

3、进程间通信的本质

进程间通信的本质,就是让不同的进程看到同一个 "资源";

这个资源是由OS分配的,本质就是一段内存

根据前面的介绍我们知道,进程之间是具有独立性的,这也就注定了其他进程是无法直接获取到其他进程的数据的!那如何在不破坏进程独立性的前提下,让不同的进程进行通信呢?其实很简单,让他们看到同一份 "资源" 就可以了!而进程是由操作系统管理的,所以这个"资源" 也一定是由操作系统提供的!所以,如果要实现进程间通信是必须要有成本的

由于,这个资源是由操作系统创建的,根据前面的介绍,操作系统不相信任何人,所以他一定会给我们提供相关的系统调用接口操作!

4、进程间通信的分类

而根据OS创建的共享资源的不同,操作系统提供的系统调用接口也就不同,即进程间通信的种类也就不同!可以将通信种类分为三类:

• 管道(pipe)

        • 匿名管道

        • 命名管道

• System V IPC

         • System V 消息队列

         • System V 共享内存

         • System V 信号量

• POSIX IPC

       • 消息队列

       • 共享内存

       • 信号量

       • 互斥量

       • 条件变量

       • 读写锁

二、管道

1、管道概念

• 管道是Unix种最古老的进程间通信的形式!

• 我们把从一个进程连接到另一个进程的一个数据流称为管道!

• 管道分为匿名管道命名管道

2、管道的原理

• 站在内核角度深度理解管道

我们知道在进程打开一个文件时,OS会为其创建一个struct file对象,以及创建一张文件按描符表(files_struct)用其中的fd_array数组记录打开了那些文件!并会为每一个struct file创建内核级缓冲区!最后当I\O结束后,将内核缓冲区写到磁盘!


如果两次用不同的方式打开同一个文件呢(第一次以r方式打开,第二次以w方式打开)?

我们根据上面及以前的介绍知道,第一次方式打开会先创建struct file对象,然后将内容加载到内核缓冲区!而由于打开的方式不一样,OS会再创建一个struct  file对象,因为是同一个文件对象且第一次已经加再到缓冲区了,所以第二次打开仅仅是让以w打开的struct file对象指向文件的属性和缓冲区,不会在重复的加载一份了

如果让当前以不同方式打开同一个文件的进程创建一个子进程呢?

因为进程具有独立性,所以子进程会有自己的PCB, files_struct等,这些内核数据结构的内容大部分继承与父进程,但是并不会将虚拟文件系统的那部分再给子进程拷贝一份了,原因是进程具有独立性,但是虚拟文件系统没有说有独立性!所以此时,父子进程形成了浅拷贝(文件系统内每个struct file对象的引用计数都是2),也就是此时父子进程指向同一块空间,即两个不同的进程看到了同一块资源!

把这种基于虚拟文件系统让多个进程看到同一份资源的文件(包含缓冲区)叫做管道文件!

管道只允许单向通信,即要么父一直给子发,要么子一直给父发!如果管道进行双向通信会出现信息紊乱的情况!其实这就像我们的水管,你要么A端流向B端,要么从B端流向A端。你不可能说两端同时相向流入!所以,此时我们就可以将上述的父子进程,中的其中一个打开文件的struct file对象给关掉了:

因为管道文件只是用来通信的,不需要向磁盘刷新,所以,此时就得重新设计管道的接口了,得把想磁盘刷新的功能给去掉,只需要让OS开辟一个内存级的缓冲区即可!

这样就可以实现不同进程通过管道进行单向通信了 !

• 理解以前不能理解的现象

OK,介绍完这里我们也就能理解以前的几个现象了!

进程会默认打开0\1\2,他是如如何做到的?

父进程bash默认打开了,我们平时所有的进程都是bash的子进程,都继承了父进程的文件描述表

为什么父子进程会向同一个显示器打印数据呢?

因为父进程默认打开了0、1、2,子进程继承了下来,所以他两指向同一个显示器文件,写的时候当然就写到同一个显示器喽~!

• 为什么我们子进程close等的时候,不影响父进程的使用呢?

因为在struct file对象内部会存在一个引用计数,记录了与多少个进程指向当前的struct file,只有当引用计数是1的时候执行close再会真正的关闭!因为父子进程同时指向,所以引用计数一定大于1,所以子进程close只是让引用计数减1,并没有真的关闭!

• 站在文件描述符的角度理解管道

• 两个进程在通信时父进程会先创建管道,本质就是以读和写的方式打开了同一个文件!所以有两个文件文件描述符fd(详细的后面使用介绍参数时介绍)!

• 父进程再去创建子进程,让他们保证看到同一份资源!

• 关掉父子进程各自不需要的文件描述符,然后就可以单向的通信了!

看完这个更加直观的文件描述符原理,我们可以看,不同进程要想进行通信首先是要申请系统的内存资源,即进程间通信是有成本的!

3、匿名管道

在创建时没有与磁盘中的文件进行关联的管道文件(没有名字),叫做匿名管道。

• 匿名管道的系统调用接口

OK,了解了原理,接下来我们来认识一下OS提供的系统调用接口:

函数的原型和返回值都介绍了,但是这个参数是啥呢?我们下面就来介绍一下pipe的参数:

pipefd[2]数组是一个输出型参数!目的是为了方便用户使用!

其中,OS默认:pipefd[0]表示读端;pipefd[1]表示写端;当然这个只是OS默认的,你也可以改成pipefd[0]表示写端;pipefd[1]表读端

OK,介绍完了接口,那就可以使用喽:我们可以让父进程读取,子进程写入:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string>

const int size = 1024;

std::string GetOtherMessage()
{
    static int cnt = 0;//消息编号
    std::string id = std::to_string(cnt++);//将消息编号转为字符串
    pid_t self_id = getpid();//获取当前进程的pid
    std::string prs_id = std::to_string(self_id);

    //将消息编号、pid拼接在一起
    std::string message = "msg_id: " + id + "  my pid: " + prs_id;
    return message;
}

// 子进程写入
void ChildProcessWrite(int wfd)
{
    std::string msg = "father, i am your child....";
    while (1)
    {
        std::string info = msg + GetOtherMessage();
        write(wfd, info.c_str(), info.size());
        sleep(1);
    }
}

// 父进程读取
void FatherProcessRead(int rfd)
{
    char buffer[size];
    while (1)
    {
        ssize_t n = read(rfd, buffer, sizeof(buffer) -1);
        if(n > 0)
        {
            buffer[n] = '\0';
            std::cout << "Son, i get: " << buffer << std::endl;
            sleep(1);
        }
        else if(n < 0)
        {
            std::cerr << "read error" << std::endl;
            break;
        }
        else
        {
            std::cerr << "Writer haved quit..., return val is: " << n << std::endl;
            break;
        }
    }
}

int main()
{
    // 创建管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    // 检查是否创建成功
    if (n != 0)
    {
        std::cerr << "errno: " << errno << "errmsg: " << strerror(errno) << std::endl;
        return -1; // 管道创建失败
    }

    // 创建子进程
    pid_t id = fork();
    // child -> write
    if (id == 0)
    {
        std::cout << "我是子进程, 正在关闭不需要的fd, 要准备发消息了..." << std::endl;
        close(pipefd[0]);             // 关闭不需要的读端
        ChildProcessWrite(pipefd[1]); // 子进程写入
        close(pipefd[1]);             // 通信完毕,关闭写端
        exit(0);
    }

    // father -> read
    std::cout << "我是父进程, 正在关闭不需要的fd, 要准备接收消息了~~~" << std::endl;
    close(pipefd[1]);             // 关闭写端
    FatherProcessRead(pipefd[0]); // 父进程读取
    close(pipefd[0]);             // 通信完毕,关闭读端

    // 等待子进程退出
    int status = 0;                      // 获取子进程的退出信息
    pid_t rid = waitpid(id, &status, 0); // 阻塞等待子进程退出
    if (rid > 0)
    {
        std::cout << "wait success" << std::endl;
        std::cout << "quit sig is: " << (status & 0x7f) << std::endl;
        std::cout << "quit code is: " << ((status >> 8) & 0xff) << std::endl;
    }

    std::cout << "father quit..." << std::endl;

    return 0;
}

OK,我们再写一个makefile方便编译运行

Test_pipe:Test_pipe.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm Test_pipe

OK,看一下效果:

我们可以看到父进程读取到的,和子进程写入的一样!另外,由于我的某里云是体验的服务器,马上过期了,所以用的是root,一般情况不建议使用root!!

• 管道的机制

• 如果写进程没有关闭,管道内空的,读进程会被阻塞,等管道有数据了,读进程才会被唤醒!

OK,我们可以让写入端不写,让读取端一直读:

• 管道被写满了,但是读进程就是不读且没有关闭,写进程会被阻塞,等到度进程读了后写进程才可以,继续进行写入!

#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <stdlib.h>
#include <sys/wait.h>

void Writer(int wfd)
{
    char c = 'a';
    size_t cnt = 0;
    while (1)
    {
        write(wfd, &c, 1);
        std::cout << "pipsize: " << ++cnt << "  -> " << c << std::endl;
        if (c == 'z' + 1)
        {
            c = 'a';
        }
        c++;
    }
}

void Reader(int rfd)
{
    char buffer[1024];
    while (1)
    {
        int n = read(rfd, buffer, 1023);
        if (n > 0)
        {
            buffer[n] = '\0';
            std::cout << "Son, i get:  " << buffer << std::endl;
            sleep(1);
        }
    }
}

int main()
{
    // 打开管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    if (n != 0)
    {
        std::cerr << "errno: " << errno << "err_msg: " << strerror(errno) << std::endl;
    }

    // 创建子进程
    pid_t id = fork();
    if (id == 0)
    {
        std::cout << "我是子进程, 正在关闭不需要的fd, 要准备发消息了..." << std::endl;
        close(pipefd[0]); // 关闭读端
        Writer(pipefd[1]);
        close(pipefd[1]); // 通信结束
    }

    // 父进程
    //  father -> read
    std::cout << "我是父进程, 正在关闭不需要的fd, 要准备接收消息了~~~" << std::endl;
    close(pipefd[1]); // 关闭写端;
    sleep(10);
    Reader(pipefd[0]);

    close(pipefd[0]); // 通信完毕,关闭读端

    // 等待子进程退出
    int status = 0;                      // 获取子进程的退出信息
    pid_t rid = waitpid(id, &status, 0); // 阻塞等待子进程退出
    if (rid > 0)
    {
        std::cout << "wait success" << std::endl;
        std::cout << "quit sig is: " << (status & 0x7f) << std::endl;
        std::cout << "quit code is: " << ((status >> 8) & 0xff) << std::endl;
    }

    return 0;
}

我们此时可以每次向管道写入一个字符,读进程先休眠10秒不读取,此时写进程当写满管道后就阻塞了:

这里卡在了65536字符处,这其实就是我们当前管道的大小, 65536Byte就是64KB

当10秒过后,父进程可以读取了:

我们发现这怎么读的数据连在一块了?其实这是管道的一个特征:管道是面向字节流的!

什么意思呢?简单说就是,读进程读的时候管道有多少就读多少,而读到的这些可能是写进程写了好多次的结果!但是读进程不管,在他看来就是一个个的字符;这就像我们一个水管一样,我们一打开可能是另一端灌入多次的结果,但是我们一次就都拿到了!至于用户怎么区分那是用户的事情!

等管道中的内容被读进程拿走了后,写进程就可以被唤醒继续写入了:

• 读端正常读,写端关闭,读端read就会返回0,表明读到了文件(pipe)结尾;

• 写端正常写入,读端关闭,操作系统会向正在写入的进程发送13号信号终止。

void Writer(int wfd)
{
    char c = 'a';
    size_t cnt = 0;
    while (1)
    {
        write(wfd, &c, 1);
        std::cout << "pipsize: " << ++cnt << "  -> " << c << std::endl;
        if (c == 'z')
        {
            c = 'a';
        }
        c++;
    }
}

void Reader(int rfd)
{
    char buffer[1024];
    int cnt = 5;
    while (cnt--)
    {
        int n = read(rfd, buffer, 1024);
        if (n > 0)
        {
            buffer[n] = '\0';
            std::cout << "Son, i get:  " << buffer << std::endl;
            sleep(1);
        }
        else if (n == 0)
        {
            std::cerr << "读取到了文件的结尾" << std::endl;
            break;
        }
        else
        {
            std::cout << "读取错误" << std::endl;
            break;
        }
    }
}

这里写端一直写入,读端读取5次后结束

• 管道的特征

• 匿名管道只能进行具有血缘关系的进程之间通信,常用于父子间进程通信

此时就是爷孙进程通信了!

• 管道内部自带进程同步机制,多执行流的时候,具有明显的顺序性!

其实有了上面的机制介绍,这一点很好理解,当管道写满了就不能写了,只能让读进程来执行喽!同理,没有读端就不读了,写段就写喽!

• 管道文件的生命周期是随进程的

• 管道文件在通信的时候是面向字节流的,即读取和写入不是一一匹配的

• 管道文件的通信模式是,一种特殊的半双工模式(就是通信是单向的)

这里唯一要解释的一个点是,半双工模式。后面介绍网络的时候还会介绍全双工模式以及单工模式!OK,这里就简单的举个例子理解一下:半双工就是对讲机对方说的时候你不能说;全双工就是你打游戏时,队友问候你的同时你也可以问候队友!单工模式:就是接收方只能接收,发送方只能发送!例如广播等;

匿名管道的应用
1、命令行执行的

我们以前就在Linux指令那一期介绍过,说 | 这个叫管道,其实它就是我们刚刚介绍的匿名管道!我们但是说,可以将他左端的处理结果和水流一样直接到右边执行!OK,可以举个例子:

 while :; do ps axj | head -1 && ps axj | grep Test_pipe | grep -v grep; sleep 1; done

我们以前写的这个脚本就是组好的例子!以及统计当前机器的用户数:

who | wc -l
2、进程池

我们可以通过上面的介绍,实现一个进程池!什么是进程池?我们以前了解过内存池,内存池是为了减少系统开辟内存的花销,直接提前一次性开辟很多内存,等用的时候直接用就可以了!这里进程池也是一样的,我们为了减少系统穿甲进程的花销,可以预先创建很多的进程等待有任务的时候直接从进程池指派进程执行即可!

如何做呢?我们进程池无非就是想节约资源,再用的时候效率高一点!那我们的大思路就是:

1、先创建一批进程并为其创建对应的管道

2、父进程通过管道,控制子进程执行任务

3、等执行完任务了,关闭管道,以及等待子进程结束

上面的思路细分一下就是,

• 可以将子进程和管道用类进行先描述,然后用数组进行组织,以后父进程对子进程和对应管道的管理就变成了对数组的管理!

• 父进程有任务时,找一个空闲的管道,向管道写入操作码,子进程读取到操作码后就去执行相应的任务!

• 等执行完了,先把对应的写端关闭,因为管道的特性,当写端关闭读端,读端就会读到文件的结尾!然后我们再去把子进程给一一等待即可!

上面的思路就是这张图:

processpool.cc 

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp"

class Channel
{
public:
    Channel(int wfd, pid_t subprocessid, const std::string &channelname)
        : _wfd(wfd), _subprocessid(subprocessid), _channelname(channelname)
    {}

    ~Channel() {}

    int getWfd()
    {
        return _wfd;
    }

    pid_t getSubprocessId()
    {
        return _subprocessid;
    }

    std::string getChannerlName()
    {
        return _channelname;
    }

    void CloseChannel()
    {
        close(_wfd);
    }

    void Wait()
    {
        pid_t rid = waitpid(_subprocessid, nullptr, 0);
        if (rid > 0)
        {
            std::cout << "wait: " << rid << " success!" << std::endl;
        }
    }

private:
    int _wfd;
    pid_t _subprocessid;
    std::string _channelname;
};

// 创建信道和子进程
void CreaterChannelAndSub(std::vector<Channel> *channels, int num, task_t task)
{
    for (int i = 0; i < num; i++)
    {
        // 创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        if (n < 0)
        {
            std::cerr << "errno: " << errno << "errmsg: " << strerror(errno) << std::endl;
            exit(-1);
        }

        // 创建子进程
        pid_t id = fork();
        if (id == 0)
        {
            // child -> read
            close(pipefd[1]); // 关掉不需要的fd
            dup2(pipefd[0], 0);
            task();
            close(pipefd[0]); // 通信结束
            exit(0);
        }

        // father
        close(pipefd[0]);

        std::string channel_name = "channel_id: " + std::to_string(i);
        channels->push_back(Channel(pipefd[1], id, channel_name));
    }
}

// 获取信道
int NextChannel(int channelnum)
{
    static int next = 0;
    int channel = next;
    next++;
    next %= channelnum;
    return channel;
}

// 给对应的信道发操作码
void SendTaskCommand(Channel &channel, int task_code)
{
    write(channel.getWfd(), &task_code, sizeof(task_code));
}

// 控制进程一次
void CtrlProcessonce(std::vector<Channel> &channels)
{
    sleep(1);
    int task_code = SelectTask();                        // 选择一个任务码
    int channel_index = NextChannel(channels.size());    // 选择一个信道
    SendTaskCommand(channels[channel_index], task_code); // 发送
    std::cout << std::endl;
    std::cout << "task_code: " << task_code << "  channel : " << channels[channel_index].getChannerlName() << "  sub_pid: "
              << channels[channel_index].getSubprocessId() << std::endl;
}

// 控制进程
void CtrlProcess(std::vector<Channel> &channels, int times = -1)
{
    if (times == -1)
    {
        while (true)
        {
            CtrlProcessonce(channels);
        }
    }
    else
    {
        while (times--)
        {
            CtrlProcessonce(channels);
        }
    }
}

// 关闭信道和等待子进程退出
void CleanUpChannel(std::vector<Channel> &channels)
{
    //  关闭所有的写端
    for (auto &channel : channels)
    {
        channel.CloseChannel();
    }

    // 等待子进程
    for (auto &channel : channels)
    {
        channel.Wait();
    }
}

int main(int argc, char *argv[])
{
    // 判断是否命令行参数合法
    if (argc != 2)
    {
        std::cerr << "command error" << std::endl;
        return -1;
    }

    // 获取进程池的数量
    int num = std::stoi(argv[1]);

    std::vector<Channel> channels;
    LoadTask(); // 加载任务
    // 1、创建信道和子进程
    CreaterChannelAndSub(&channels, num, work);

    // 2、通过channel控制子进程
    // a.选择任务 b.选择一个进程
    CtrlProcess(channels, 5);

    // 3、关闭管道和子进程
    CleanUpChannel(channels);

    // test
    // for (auto &channel : channels)
    // {
    //     std::cout << "----------------------------------------------" << std::endl;
    //     std::cout << "channel_id: " << channel.getSubprocessId() \
    //     << "  channel_name: " << channel.getChannerlName() << "  Sub_pid: " \
    //     << channel.getWfd() << std::endl;
    // }

    // sleep(100);
    return 0;
}

Task.hpp

#pragma once
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <unistd.h>

#define NUM 3

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

// 下载
void DownLoad()
{
    std::cout << "i am DownLoad task" << std::endl;
}

// 输出
void Print()
{
    std::cout << "i am Print task" << std::endl;
}

// 刷新
void Flush()
{
    std::cout << "i am Flush task" << std::endl;
}

task_t tasks[NUM]; // 创建任务的指针指针数组
// 加载任务
void LoadTask()
{
    srand(time(nullptr) ^ 177777777);
    tasks[0] = Print;
    tasks[1] = DownLoad;
    tasks[2] = Flush;
}

// 执行任务
void ExcuteTask(int num)
{
    if (num > 2 || num < 0)
        return;
    tasks[num]();
}

int SelectTask()
{
    return rand() % NUM;
}

// 子进程执行的任务
void work()
{
    while (true)
    {
        int command = 0;
        int n = read(0, &command, sizeof command);
        if (n == sizeof(command))
        {

            std::cout << "pid is: " << getpid() << "task: " << std::endl;
            ExcuteTask(command);
            std::cout << "hehe" << std::endl;
        }
        else if (n == 0)
        {
            std::cout << "sub process :" << getpid() << " is quit...." << std::endl;
            break;
        }
        else
        {
            std::cerr << "error" << std::endl;
            break;
        }
    }
}

4、命名管道

我们上面介绍了,匿名管道!它适用于具有血缘关系的进程间通信!现在我们的需求是,要让两个毫不相关的进程通信,该如何做呢?此时匿名管道就不行了,得用命名管道!

• 什么是命名管道?

让不相关的进程之间交换数据的管道文件叫做命名管道

• 在命令行创建一个命名管道

我们可以使用 mkfifo 指令在命令行创建一个命名管道:

例如:

此时,echocat就是两个不同的进程,我们可以看到通过mkfifo创建的命名管道myfifo可以进行通信了!如果我们不想要了,直接rm删掉或这可以用unlink删除:

• mkfifo函数创建命名管道

除了上述的在命令行用指令创建命名外也可以,在代码中自主的利用函数mkfifo创建:

当然,你如果不想要了,也可以用函数unlink给删除掉:

OK,我们写一个代码用一下:我们让client端写,让server端读:

named_pipe.hpp

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <fcntl.h>

#define User 1
#define Creater 2
#define Default -1
#define Read O_RDONLY
#define Write O_WRONLY
#define SIZE 4096

const std::string comm_path = "./myfifo"; // 通信文件的路径

class NamedPipe
{
private:
    bool OpenNamePipe(int mode) // 打开文件
    {
        _fd = open(_path_name.c_str(), mode);
        if (_fd < 0)
        {
            return false;
        }

        return true;
    }

public:
    NamedPipe(const std::string &path, int who) // 构造
        : _path_name(path), _id(who), _fd(Default)
    {
        if (who == Creater)
        {
            int n = mkfifo(path.c_str(), 0666);
            if (n != 0)
            {
                perror("mkfifo");
                exit(-1);
            }

            std::cout << "creater namedpipe success" << std::endl;
        }
    }

    ~NamedPipe() // 析构
    {
        if (_id == Creater)
        {
            int res = unlink(_path_name.c_str());
            if (res != 0)
            {
                perror("unlink");
            }

            std::cout << "creater free named pipe" << std::endl;
        }

        if (_fd != Default)
            close(_fd);
    }

    bool OPenForRead() // 以读的方式打开
    {
        return OpenNamePipe(Read);
    }

    bool OPenForWrite() // 以写的方式打开
    {
        return OpenNamePipe(Write);
    }

    int ReadNamePipe(std::string *out) // 读管道
    {
        char buffer[SIZE];
        int n = read(_fd, buffer, sizeof(buffer));
        if (n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }

    int WriteNamePipe(const std::string &in) // 写管道
    {
        return write(_fd, in.c_str(), in.size());
    }

private:
    std::string _path_name; // 管道文件的路径
    int _id;                // 操作者
    int _fd;                // 文件描述符
};

server.cc

#include "named_pipe.hpp"

void ReadMessage(NamedPipe &fifo)
{
    while (true)
    {
        std::string message;
        int n = fifo.ReadNamePipe(&message);
        if (n > 0)
        {
            std::cout << "Client Say> " << message << std::endl;
        }
        else if (n == 0)
        {
            std::cout << "Client Quit, Server Too" << std::endl;
            break;
        }
        else
        {
            std::cout << "fifo.ReadNamePipe Error" << std::endl;
            break;
        }
    }
}

// read
int main()
{
    NamedPipe fifo(comm_path, Creater);
    if (fifo.OPenForRead())
    {
        // 读文件
        ReadMessage(fifo);
    }
    return 0;
}

client.cc

#include "named_pipe.hpp"

// 写一次
void WriteMessageOnce(NamedPipe &fifo)
{
    std::string msg;
    std::cout << "Please Enter> " << std::endl;
    std::getline(std::cin, msg);
    fifo.WriteNamePipe(msg);
}

// 写消息
void WriteMessage(NamedPipe &fifo, int times = -1)
{
    if (times == -1)
    {
        while (true)
        {
            WriteMessageOnce(fifo);
        }
    }
    else
    {
        while (times--)
        {
            WriteMessageOnce(fifo);
        }
    }
}

// write
int main()
{
    NamedPipe fifo(comm_path, User);
    if (fifo.OPenForWrite())
    {
        // 写消息
        WriteMessage(fifo);
    }

    return 0;
}

makefile

.PHONY:all
all:client server

client:client.cc
	g++ -o $@ $^ -std=c++11
server:server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf client server

看一下效果:

• 命名管道的原理

命名管道的原理和上面介绍的管道原理是一样的,上面那之所以没有说是匿名还是命名的原因是他们的原理是一样的!只不过一个是没有名字的,一个是有名字的,但是他们的底层原理一致!

5、匿名管道和命名管道的区别

• 匿名管道由pipe函数创建而成

• 命名管道由mkfifo函数创建,打开用open

• FIFO(命名管道)和pipe(匿名管道)之间的唯一区别就是他们的创建和打开方式不同,一旦这些工作完成之后他们具有相同的语义!

• 匿名管道用于具有血缘关系的进程之间,而命名管道用于两个毫不相干的进程

OK,本期分享就到这里,我是cp,期待与你的下次相遇!

结束语:编程路上,勤奋为翼,无畏前行!

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

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

相关文章

基于Java+SpringBoot+Vue的教师工作量管理系统

基于JavaSpringBootVue的教师工作量管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345…

OpenAi not returning result and exited with code=0

题意&#xff1a;OpenAi 没有返回结果&#xff0c;程序以代码 0 退出 问题背景&#xff1a; Im trying to use OpenAI, but I cant get a result. Im accessing the API via Visual Studio Code. I have downloaded these extensions for Visual Studio Code: Code Runner and…

Bash考古以及 StackOverflow的2024年度技术统计报告

0.缘起 前段时间&#xff0c;有一次调试.sh时废了好大功夫&#xff0c;单独执行各行指令&#xff0c;可以&#xff0c;但是存储为.sh就不行了&#xff0c;最终发现&#xff0c;我漏加了文件头部的那个声明&#xff1a; #!/bin/bash https://wikimili.com/en/Stephen_R._Bourne…

代发考生战报:考试通过 H12-831科目

代发考生战报&#xff1a;考试通过 H12-831科目&#xff0c;同事2人分别2天考HCIP续认证&#xff0c;考试题基本都是题库里的&#xff0c;印象有1-2个题是新题&#xff0c;也许是自己没记准&#xff0c;题库更新很及时&#xff0c;题库看会了考试很简单&#xff0c;考试半个小时…

认识Modbus RTU与Modbus TCP

&#xff08;选自成都纵横智控-Modbus RTU与Modbus TCP协议区别详解 &#xff09; Modbus RTU 和 Modbus TCP 是两种常用的工业通信协议&#xff0c;用于连接电子设备&#xff0c;但它们在多方面有所不同。以下是它们的详细比较&#xff1a; Modbus RTU 协议类型&#xff1a; …

【精通Redis】Redis持久化和复制

文章目录 前言一、Redis持久化1.1 RDB快照手动触发自动触发save和bgsave的区别 1.2 AOF持久化appendonly配置 二、Redis复制2.1 开启Redis主从复制2.2 Redis复制的启动过程 前言 本文主要讨论Redis的持久化方式和复制特性。Redis的持久化方式有两种&#xff0c;一种叫RDB&…

越秀大悦城·天悦海湾 | 繁华底色 北方头等地标

每一座骄傲的城市&#xff0c;都以奔涌向前的气魄&#xff0c;屹立时代潮头。每一处让城市仰望的居所&#xff0c;亦怀揣与城市共美好的磐心&#xff0c;以焕新的生活方式致敬理想生活家。 越秀大悦城天悦海湾&#xff0c;踞青岛北站旁创新创业活力区&#xff0c;以优越生态基…

望获实时Linux与EtherCAT的硬实时解决方案

在追求极致实时性与可靠性的工业自动化领域&#xff0c;望获实时Linux以其卓越的实时性能和广泛的兼容性&#xff0c;正逐步成为工业控制领域的核心力量。结合EtherCAT这一高效通信协议&#xff0c;我们共同打造了一套创新的硬实时工业控制方案&#xff0c;旨在满足现代工业对快…

怎样批量音频格式转换?5个方法帮你搞定

炎炎夏日的到来&#xff0c;大学生们也迎来了期盼已久的暑期生活。对于无论是计划外出旅行&#xff0c;还是宅在家中享受悠闲时光的朋友们来说&#xff0c;音乐总是不可或缺的伴侣。 然而&#xff0c;面对不同设备对音频格式的要求&#xff0c;如何轻松转换音频格式&#xff0…

数组下标越界异常(ArrayIndexOutOfBoundsException)以及解决方案

在Java学习的初期&#xff0c;我们往往可能会遇到一些程序的错误提示&#xff0c;告诉我们&#xff0c;程序出现了某些不正常的情况&#xff0c;在这种情况发生时&#xff0c;我们一般称之为出现了异常。 我们目前有两类常见的错误&#xff1a; 一个是编译时异常 &#xff0c…

PicGo + gitee 免费搭建个人图床

目录 1 图床概念2 使用gitee和PicGo搭建图床流程2.1 下载安装PicGo工具 3 图片上传错误处理3.1 PicGo客户端提示404错误信息图片上传失败3.2 PicGo客户端提示400错误信息图片上传失败 1 图床概念 ​ "图床"是一个网络术语&#xff0c;它指的是一种用于存储和托管图片…

springboot基于微信小程序的旅游攻略-计算机毕业设计源码96432

目录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.1.1技术可行性 2.1.2经济可行性 2.1.3操作可行性 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 …

实用python代码之修改图片大小

前言 通过这段代码可以修改图片的像素尺寸大小 运行截图如下 代码如下&#xff1a; import tkinter as tk from tkinter import filedialog, messagebox, colorchooser from PIL import Image, ImageDrawclass ImageProcessorApp:def __init__(self, root):self.root rootse…

审稿速度奇慢的大佬期刊?到底值不值得投?

关注GZH【欧亚科睿学术】&#xff0c;第一时间了解期刊最新动态&#xff01; &#x1f525; &#x1f525; &#x1f525; &#x1f525; 中科院1区SCI&#xff0c;各指标优秀&#xff01; 今天小编给大家介绍的是一本计算机科学领域的大佬期刊《Swarm and Evolutionary…

自驾畅游保定:参观总督署,品美食文化

这是学习笔记的第 2490篇文章 前几天跟孩子聊天&#xff0c;孩子说暑假都没出去玩了&#xff0c;暑假旅行的作业咋写&#xff1f;让我有满满的负疚感&#xff0c;去附近的公园、吃点美食不算旅游&#xff0c;得了&#xff0c;得安排一下一日游。 几个月前心心念的去保定&#x…

叉车AI监控影像防撞系统:减少损失,预防碰撞

叉车&#xff0c;这企业物流中的坚实力量&#xff0c;其安全运作是企业稳健前行的基石。在追求高效与效益的浪潮中&#xff0c;如何最大化地规避叉车事故&#xff0c;已悄然跃升为企业管理的头等大事。随着市场监管总局重锤落下&#xff0c;新版《场(厂)内专用机动车辆安全技术…

你一定想看的LVS详细介绍及常见模式(NAT,DR,防火墙标记)实验详解

目录 一、什么是LVS 二、LVS的核心思想 三、 LVS的优势 四、LVS的调度算法 4.1. LVS的调度算法类型 4.2. LVS静态调度算法 4.3. LVS动态调度算法 4.4.在4.15版本内核以后新增调度算法 五、LVS软件相关信息 六、ipvsadm命令 七、 LVS的NAT模式实验详解 7.1实验环境 7.…

使用Github Pages结合MkDocs制作个人网站

目录 环境配置 创建网页仓库 创建Github仓库 创建本地仓库 本地运行网页 配置主题 网站部署到Github docs文件夹介绍 修改主页显示内容 添加新文件 mkdocs.yml文件介绍 其他效果 环境配置 为了正确的安装Mkdocs&#xff0c;需要先下载Python环境 下载地址&#x…

7日 ROI 提升150%,NetMarvel 助力越南厂商 Mirai 旗下解谜休闲手游实现高质增长!

解谜游戏是目前全球最受欢迎的休闲细分品类之一。对于厂商来说&#xff0c;解谜游戏开发成本低、制作周期短&#xff0c;打造成为爆款的几率更大。据 Sensor Tower 发布的数据&#xff0c;解谜游戏在全球市场累计的下载量已经超过500亿次&#xff0c;累计收入也已突破420亿美元…