有名管道,有名管道API,有名管道实现进程的单向通信(本机IPC)【linux】(zx)

有名管道

为什么叫“有名管道”

无名管道因为没有文件名,被称为了无名管道,同样的道理,有名管道之所以叫“有名管道”,是因为它有文件名。
也就是说当我们调用相应的API创建好“有名管道”后,会在相应的路径下面看到一个叫某某名字的“有名管道文件”。
不管是有名管道,还是无名管道,它们的本质其实都是一样的,它们都是内核所开辟的一段缓存空间。
进程间通过管道通信时,本质上就是通过共享操作这段缓存来实现,只不过操作这段缓存的方式,是以读写文件的形式来操作的。

有名管道特点

能够用于非亲缘进程之间的通信

因为有文件名,所以进程可以直接调用open函数打开文件,从而得到文件描述符,不需要像无名管道一样,必须在通过继承的方式才能获取到文件描述符。

所以任何两个进程之间,如果想要通过“有名管道”来通信的话,不管它们是亲缘的还是非亲缘的,只要调用open函数打开同一个“有名管道”文件,然后对同一个“有名管道文件”进行读写操作,即可实现通信。

A进程 —————————> 有名管道 ————————> B进程

总之,不管是亲缘进程还是非亲缘进程,都可以使用有名管道来通信。

进入阻塞

读管道时,如果管道没有数据的话,读操作同样会阻塞(休眠)

读端口被关闭的处理

当进程写一个所有读端都被关闭了的管道时,进程会被内核返回SIGPIPE信号如果不想被该信号终止的话,我们需要忽略、捕获、屏蔽该信号。
不过一般情况下,不需要对这个信号进行处理。

有名管道的使用步骤

(1)进程调用mkfifo创建有名管道
(2)open打开有名管道
(3)read/write读写管道进行通信

对于通信的两个进程来说,创建管道时,只需要一个人创建,另一个直接使用即可。
为了保证管道一定被创建,最好是两个进程都包含创建管道的代码,谁先运行就谁先创建,后运行的发现管道已经创建好了,那就直接open打开使用。

有名管道API

API说明

函数原型

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

功能
创建有名管道文件,创建好后便可使用open打开。
如果是创建普通文件的话,我们可以使用open的O_CREAT选项来创建,比如:
open("./file", O_RDWR|O_CREAT, 0664);

但是对于“有名管道”这种特殊文件,这里只能使用mkfifo函数来创建。

参数
pathname:被创建管道文件的文件路径名。
mode:指定被创建时原始权限,一般为0664(110110100),必须包含读写权限。
使用open函数创建普通文件时,指定原始权限是一样的。
open("./file", O_RDWR|O_CREAT, 0664);
创建新文件时,文件被创建时的真实权限=mode & (~umask)
umask是文件权限掩码,一般默认为002或者022
mkfifo(“./fifo”, 0664);

返回值
成功返回0,失败则返回-1,并且errno被设置。

有名管道实现进程的单向通信

代码演示

有两个进程想要通信,而且还是非亲缘进程,此时我们就可以使用“有名管道”来通信。
我们首先在当前代码创建一个管道文件:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#define FIFO_NAME1 "./fifo1"
void print_err(char * estr)
{
        perror(estr);
        exit(-1);
}

int main()
{
        char buf[100] = {0};
        int ret = 0;
        int fd = 0;
        ret = mkfifo(FIFO_NAME1,0664);
        if(ret == -1) print_err("mkfifo fail");
        fd = open(FIFO_NAME1,O_WRONLY);
        if(fd == -1) print_err("open fail");
        while(1)
        {
                bzero(buf,sizeof(buf));
                scanf("%s",buf);
                write(fd,buf,sizeof(buf));
        }
        return 0;
}

运行结果为:

在这里插入图片描述

我们再次运行程序:
在这里插入图片描述

我们可以看到管道文件已经存在,那么我们就可以知道mkfifo这个函数在创建管道文件的时候如果管道文件不存在的时候就会出错返回,并且打印文件已经存在的错误。

那么我们对于上面代码进行修改:
把代码:

if(ret == -1) print_err("mkfifo fail");

修改为:

if(ret == -1 && EEXIST	) print_err("mkfifo fail");

那么我们当文件存在的时候我们不需要程序去报错退出。

那么我们需要的是:
如果文件不存在就创建文件并且在后面进行打开,如果文件已经存在就直接跳过直接执行后面代码。那么文件存在就不会报错并且返回退出进程。那么就上面修改之后就只会报文件存在以外的错误,也就是说我们不认为文件存在时已经错误。

那么当我们进程运行起来之后我们需要通过管道来进行通信,进程结束之后我们就没有进程通信的需要了,那么我们就在进程运行结束的时候把管道文件删除,那么通过我们按下ctrl+c进行进程结束,那么我们就可以对于SIGINT信号进行捕获,然后把管道文件进行删除再退出进程。

我们把上面整个代码进行优化:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#define FIFO_NAME1 "./fifo1"
void print_err(char * estr)
{
        perror(estr);
        exit(-1);
}

void signal_fun(int signal)
{
        remove(FIFO_NAME1);
        exit(-1);
}

int main()
{
        char buf[100] = {0};
        int ret = 0;
        int fd = 0;
         signal(SIGINT,signal_fun);
        ret = mkfifo(FIFO_NAME1,0664);
        if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");
        fd = open(FIFO_NAME1,O_WRONLY);
        if(fd == -1) print_err("open fail");
        while(1)
        {
                bzero(buf,sizeof(buf));
                scanf("%s",buf);
                write(fd,buf,sizeof(buf));
        }
     
        return 0;
}

运行结果为:
在这里插入图片描述

但是signal(SIGINT,signal_fun);不能放在

fd = open(FIFO_NAME1,O_WRONLY);

代码之后,因为当前只有一个写端进行打开,管道文件的读端没有打开的时候,代码就会在
fd = open(FIFO_NAME1,O_WRONLY); 阻塞,不会运行后面的代码。

我们可以看到创建出来的管道文件,但是当前只有一个从键盘向管道文件写入数据的操作,并没有一个从从管道读取文件的操作,所以我们先将进程退出,接下来我们编写读取管道数据的代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#define FIFO_NAME1 "./fifo1"
void print_err(char * estr)
{
        perror(estr);
        exit(-1);
}

void signal_fun(int signal)
{
        remove(FIFO_NAME1);
        exit(-1);
}

int main()
{
        char buf[100] = {0};
        int ret = 0;
        int fd = 0;
        signal(SIGINT,signal_fun);
        ret = mkfifo(FIFO_NAME1,0664);
        if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");
        fd = open(FIFO_NAME1,O_RDONLY);
        if(fd == -1) print_err("open fail");
        while(1)
        {
                read(fd,buf,sizeof(buf));
                printf("rev:%s\n",buf);
        }
        return 0;
}

运行结果为:

在这里插入图片描述

我们在另一个中断窗口运行read

在这里插入图片描述

我们可以看到实现了两个进程之间数据交换。

小结:图解分析

在这里插入图片描述

上面的图和无名管道的单向数据交换逻辑是一样的,只不过创建管道文件的方法不一样。

发布了163 篇原创文章 · 获赞 94 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43648751/article/details/104720739