【Linux】命名管道

目录

原理

代码

回归概念


我们前面学习了匿名管道,解决了父子进程(具有血缘关系的进程)之间的通信,那如果两个进程毫无关系呢???

原理

在前面的匿名管道中,可以通过父子继承的方式看到同一个被打开的文件,那么在命名管道中,怎么保证两个毫不相关的进程,打开了同一个文件呢???每一个文件,都有文件路径,文件路径具有唯一性。

由于上面一个进程只是想完成进程间通信,没必要刷新到磁盘上,所以,就要求上图中磁盘上的hello.txt是一种特殊文件,这个文件被打开之后,在内存中一旦写入缓冲区,不会刷新到磁盘上,而是让两者在内存中完成内存级别的通信就可以了,这种通过路径表示通信文件唯一性的,叫做命名管道。所谓命名,就是文件有名字,文件会有路径,有路径必定有名字。 为什么又叫管道呢?因为它依旧是一个内存级的基于文件进行通信的通信方案!

代码

我们来介绍一条命令:mkfifo,(fifo即先进先出,是一个队列),我们在Linux中通过man mkfifo查看:

我们可以使用mkfifo创建命名管道:

myfifo的文件类型是p,我们尝试使用echo往myfifo写一些内容,发现卡在了那里,在另一个终端cat查看,可以看到刚才echo的内容:

echo启动后是一个进程,cat启动后也是一个进程,这就相当于实现了一次通信,

我们发现,在往命名管道写入内容后,myfifo的大小仍然是0,也就是并没有写入磁盘当中,但是两个进程间依然可以通信。

其实,我们也可以使用mkfifo接口创建命名管道,然后一个进程写,一个进程读,使用man 3 mkfifo查看其使用方法:

 

在创建出命名管道后,我们还需要能够删除管道文件,unlink指令可以删除命名管道,

unlink的参数是文件路径,通过路径删除指定路径下的文件,因此,我们在namedPipe.hpp文件中可以写两个函数,一个用于创建管道,一个用于删除管道:

#pragma once

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

const std::string comm_path = "./myfifo";

int CreateNamedPipe(const std::string& path)
{
    int res = mkfifo(path.c_str(), 0666);
    if(res < 0)
    {
        perror("mkfifo");
    }
    return res;
}

int RemoveNamedPipe(const std::string& path)
{
    int res = unlink(path.c_str());
    if(res < 0)
    {
        perror("mkfifo");
    }
    return res;
}

一般来说,我们建议由一个进程来创建和删除命名管道,即由一个进程来管理管道,将来另一个进程想和这个进程通信就不用再创建管道,而只需打开管道文件就可以,也就是一个以读方式打开,一个以写方式打开,就可以完成通信了!

为了让进程更好地管理命名管道,我们为命名管道创建一个类NamedPipe():

class NamedPipe
{
private:
    bool OpenNamedPipe(int mode)
    {
        _fd = open(_fifo_path.c_str(), mode);
        if (_fd < 0)
            return false;
        return true;
    }
public:
    NamedPipe(const std::string &path, int who)
        : _fifo_path(path), _id(who), _fd(DefaultFd)
    {
        if (who == Creater)
        {
            int res = mkfifo(path.c_str(), 0666);
            if (res < 0)
            {
                perror("mkfifo");
            }
            std::cout << "creater create named pipe" << std::endl;
        }
    }
    bool OpenForRead()
    { 
        return OpenNamedPipe(Read);
    }
    bool OpenForWrite()
    {
        return OpenNamedPipe(Write);
    }
    int ReadNamedPipe(std::string* out)
    {
        char buffer[BaseSize];
        int n = read(_fd, buffer, sizeof(buffer));
        if(n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }
    int WriteNamedPipe(const std::string& in)
    {
        return write(_fd, in.c_str(), in.size());
    }

    ~NamedPipe()
    {
        if (_id == Creater)
        {
            //sleep(5);
            int res = unlink(_fifo_path.c_str());
            if (res < 0)
            {
                perror("unlink");
            }
            std::cout << "creater free named pipe" << std::endl;
        }
        if(_fd != DefaultFd) close(_fd);
    }

private:
    const std::string _fifo_path;
    int _id;
    int _fd;
};

我们来看一种现象,先只运行读端,如果打开文件,但是写还没来,会阻塞在open调用中,直到对方打开,是一种进程同步。如果读端和写端都打开了,此时关闭读端,如果写端再写入,那么写端就会关闭,因为受到了SIGPIPE信号。综合来看,这种命名管道也遵循我们之前说的管道的所有特性!

回归概念

我们知道,进程间通信的前提是让不同的进程,看到同一份资源,对于命名管道来说,通过文件路径看到了同一份资源!

完整代码附上:

namedPipe.hpp

#pragma once

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

#define Creater 1
#define User 2
#define BaseSize 4096
#define DefaultFd -1
#define Read O_RDONLY
#define Write O_WRONLY

const std::string comm_path = "./myfifo";

class NamedPipe
{
private:
    bool OpenNamedPipe(int mode)
    {
        _fd = open(_fifo_path.c_str(), mode);
        if (_fd < 0)
            return false;
        return true;
    }
public:
    NamedPipe(const std::string &path, int who)
        : _fifo_path(path), _id(who), _fd(DefaultFd)
    {
        if (who == Creater)
        {
            int res = mkfifo(path.c_str(), 0666);
            if (res < 0)
            {
                perror("mkfifo");
            }
            std::cout << "creater create named pipe" << std::endl;
        }
    }
    bool OpenForRead()
    { 
        return OpenNamedPipe(Read);
    }
    bool OpenForWrite()
    {
        return OpenNamedPipe(Write);
    }
    int ReadNamedPipe(std::string* out)
    {
        char buffer[BaseSize];
        int n = read(_fd, buffer, sizeof(buffer));
        if(n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }
    int WriteNamedPipe(const std::string& in)
    {
        return write(_fd, in.c_str(), in.size());
    }

    ~NamedPipe()
    {
        if (_id == Creater)
        {
            //sleep(5);
            int res = unlink(_fifo_path.c_str());
            if (res < 0)
            {
                perror("unlink");
            }
            std::cout << "creater free named pipe" << std::endl;
        }
        if(_fd != DefaultFd) close(_fd);
    }

private:
    const std::string _fifo_path;
    int _id;
    int _fd;
};

server.cc

#include "namedPipe.hpp"

// server read:管理命名管道的整个生命周期
int main()
{
    NamedPipe fifo(comm_path, Creater);
    if (fifo.OpenForRead())
    {
        //对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,直到对方打开
        //进程同步
        std::cout << "server open named pipe done" << std::endl;
        sleep(3); 
        while (true)
        {
            std::string message;
            int n = fifo.ReadNamedPipe(&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.ReadNamedPipe Error" <<std::endl;
                break;
            }
        }
    }

    return 0;
}

client.cc

#include "namedPipe.hpp"

// write
int main()
{
    NamedPipe fifo(comm_path, User);
    if (fifo.OpenForWrite())
    {
        std::cout << "client open named pipe done" << std::endl;
        while (true)
        {
            std::cout << "please enter> ";
            std::string message;
            std::getline(std::cin, message);
            fifo.WriteNamedPipe(message);
        }
    }

    return 0;
}

 

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值