【Linux】进程间通信2——命名管道(FIFO)

1. 命名管道(FIFO)

1.1. 基本概念

        匿名管道只能用于具有共同祖先的进程(具有亲缘关系的进程)之间的通信,如果要实现两个毫不相关进程之间的通信,可以使用命名管道来做到。

        我们可以这么理解一下,可以将命名管道理解为 “挂名” 后的匿名管道,把匿名管道加入文件系统中,但仅仅是挂个名而已,目的就是为了让其他进程也能看到这个文件(文件系统中的文件可以被所有进程看到)

那么如何给 匿名管道 起名字呢?

  • 结合文件系统,首先要给匿名管道这个纯纯的内存文件分配 inode,这样子就能将文件名与之构建联系,关键点在于不给它分配 Data block,因为它是一个纯纯的内存文件,是不需要将数据刷盘到磁盘中的

因为没有 Data block,所以命名管道这个特殊文件大小为 0

我们可以看看这个命名管道 

 

直到我去读取了这个pipe的内容它才停止阻塞了 

普通文件是很难做到通信的,即便做到通信也无法解决一些安全问题。

事实上,命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。

        不同于匿名管道,命名管道提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中。

  1. 命名管道是一个文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。
  2. 值得注意的是,FIFO(first input first output)总是按照先进先出的原则工作,第一个被写⼊的数据将首先从管道中读出。

1.2.毫不相干的进程之间是怎么通信的

命名管道就是一种特殊类型的文件,两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。

虽然命名管道看起来很像文件,但是本质上这个文件也是一个内存文件,只不过在内存上只有一个简单的映像,永远都是 0 ,就只占了一个名字罢了。两个进程只需要通过这个名字在内存中打开文件就好了。

1.3.命名管道的工作原理

如果两个不同的进程打开同一个文件的时候,在内核中,操作系统会打开几个文件?

实际上,当重复多次打开同一个文件时,并不会费力的打开多次,而且在第一次打开的基础上,对struct file 结构体中的引用计数 ++,所以对于同一个文件,不同进程打开了,看到的就是同一个

两个进程怎么知道打开的是同一个文件?

  •  匿名管道里使用继承的方式确保打开的是同一个文件

而命名管道不一样,两个进程只需要看到同一个路径下的同名文件,就能知道打开的是同一个文件名

还记得我们最开始的时候吗?

命名管道可是有文件名的嘞!!!! 

1.4、命名管道与匿名管道的区别

不同点:

  1. 匿名管道只能用于具有血缘关系的进程间通信;而命名管道不讲究,谁都可以用
  2. 匿名管道直接通过 pipe 函数创建使用;而命名管道需要先通过 mkfifo 函数创建,然后再通过 open 打开使用
  3. 出现多条匿名管道时,可能会出现写端 fd 重复继承的情况;而命名管道不会出现这种情况

在其他方面,匿名管道与命名管道几乎一致

        两个都属于管道家族,都是最古老的进程间通信方式,都自带同步与互斥机制,提供的都是流式数据传输

2.创建命名管道

2.1.mkfifo命令

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

mkfifo 命名管道的名字

 

成功解锁了一种新的特殊类型文件:p 管道文件

这个管道文件也非常特殊:大小为 0,从侧面说明 管道文件就是一个纯纯的内存级文件,有自己的上限,出现在文件系统中,只是单纯挂个名而已

2.2.mkfifo函数 

 此外我们还可以通过相关的函数来创建,这个函数也叫mkfifo 

 参数 : mkfifo函数的第一个参数是pathname,表示要创建的命名管道文件。

  • 若pathname以路径的方式给出,则将命名管道文件创建在pathname路径下。
  • 若pathname以文件名的方式给出,则将命名管道文件默认创建在当前路径下。(注意当前路径的含义)

mkfifo函数的第二个参数是mode,表示创建命名管道文件的默认权限。

返回值:  

  •  命名管道创建成功,返回0   ;  
  • 命名管道创建失败,返回-1。

3.命名管道的使用

命名管道和匿名管道的使用方法基本是相同的。

只是使用命名管道时,必须先调用open()将其打开。

因为命名管道是一个存在于硬盘上的文件,而管道是存在于内存中的特殊文件。

需要注意的是,调用open()打开命名管道的进程可能会被阻塞。

  1. 但如果同时用读写方式( O_RDWR)打开,则一定不会导致阻塞;
  2. 如果以只读方式( O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写方打开管道;
  3. 同样以写方式( O_WRONLY)打开也会阻塞直到有读方式打开管道。

①如果当前打开操作是为读而打开FIFO时。

  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO。
  • O_NONBLOCK enable:立刻返回成功。

②如果当前打开操作是为写而打开FIFO时。

  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO。
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO。

4.通过命名管道 ,client & server进行通信

我们通信,肯定是两个不同且没有任何关系的进程进行通信,我们先把前面的工作做好了再说

comm.h中包含一些头文件供client 和 server使用

#pragma once

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

#define FIFO_FILE "./myfifo"
#define MODE 0664

enum{
    FIFO_CREAT_ERR=1,
    FIFO_DELETE_ERR,
    FIFO_OPEN_ERR
};

server.cpp

#include"common.h"

using namespace std;

int main()
{
int n=mkfifo(FIFO_FILE,MODE);
if(n==-1)
{
    perror("mkfifo failed\n");
    exit(FIFO_CREAT_ERR);
}

}

 我们编译运行一下

很好,确实创建了通信的命名管道 

我们最后用完这个命名管道的时候还要把它给删了,那么怎么删除呢?

好,我们 里面改代码

server.cpp

#include"common.h"

using namespace std;

int main()
{
    int n=mkfifo(FIFO_FILE,MODE);//创建命名管道
    if(n==-1)
    {
        perror("mkfifo failed\n");
        exit(FIFO_CREAT_ERR);
    }

    sleep(5);//5秒后
    
    int m=unlink(FIFO_FILE);//删除命名管道
    if(m==-1)
    {
        perror("unlink\n");
        exit(FIFO_CREAT_ERR);
    }
}

 我们运行这个代码

在另一个平台上面的监视情况如下

没毛病啊!!! 

资源准备完毕了,接下来就要通信了。

那我们进程怎么打开命名管道呢?

很简单open,read,write,close这四件套必须得有

server.cpp 代码

实现服务端(server)和客户端(client)之间的通信之前,我们需要先让我们让server来进行命名管道的管理,我们需要让服务端运行后创建一个命名管道文件,然后再以读的方式打开该命名管道文件,之后服务端就可以从该命名管道当中读取客户端发来的通信信息了。

#include "common.h"

using namespace std;

int main()
{
    int n = mkfifo(FIFO_FILE, MODE); // 创建命名管道
    if (n == -1)
    {
        perror("mkfifo:");
        exit(FIFO_CREAT_ERR);
    }
    
    //打开管道,等待写入方打开之后,自己才会打开文件,向后执行,open阻塞
    int fd=open(FIFO_FILE,O_RDONLY);
    if(fd<0)
    {
        perror("open:");
        exit(FIFO_OPEN_ERR);
    }

    cout<<"server open file done"<<endl;

    //开始通信
    while(true)
    {
        char buffer[1024]={0};
        int x=read(fd,buffer,sizeof(buffer));
        if(x>0)
        {
            buffer[x]=0;
            cout<<"client say:"<<buffer<<endl;
        }
        else if(x==0)//读到文件尾部了
        {
            cout<<"client quit ,me too"<<endl;
            break;
        }
        else
            break;

    }

    close(fd); //关闭管道
    
    int m = unlink(FIFO_FILE); // 删除命名管道
    if (m == -1)
    {
        perror("unlink\n");
        exit(FIFO_DELETE_ERR);
    }
}

client.c 代码

服务端已经做好了管道的管理,对于客户端来说,客户端只需要使用就行了

#include"common.h"

using namespace std;

int main()
{
    int fd=open(FIFO_FILE,O_WRONLY);//打开管道
    if(fd<0)
    {
        perror("open:");
        exit(FIFO_OPEN_ERR);
    }
    
    string line;//写东西
    while(true)
    {
        cout<<"Please enter@";
        cin>>line;

        write(fd,line.c_str(),line.size());
    }

    close(fd);//关闭管道
}

④运行结果

这个通信要求先执行server,因为它是创建管道的

我们运行server 

这个管道文件创建出来了

我们再运行./client

通过ps命令查看这两个进程的信息,可以发现这两个进程确实是两个毫不相关的进程,因为它们的PID和PPID都不相同 

 client 和 server 谁先退出问题

如果client先退出,server将管道当中的数据读完后就再也读不到数据了,那么此时server也就会去执行它的其他代码了(在当前代码中是直接退出了)。

如果server先退出client写入管道的数据就不会被读取了,也就没有意义了,那么当client下一次再向管道写入数据时,就会收到操作系统发来的13号信号(SIGPIPE),此时client就被操作系统强制杀掉了。

⑥通信是在内存当中进行的

client端代码不变 ,server端只是以读的方式打开,但是不读取数据  :

 运行程序前后两次查看myfifo管道文件的大小始终为0,说明了双方进程之间的通信依旧是在内存当中进行的,和匿名管道通信是一样的。

  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值