APUE学习之管道(pipe)与命名管道(fifo)

news2025/1/11 5:55:05

目录

一、简介

二、管道(Pipe)

1、管道的基本概念

2、管道的局限性

3、管道的创建

4、管道的读写规则

5、实战演练

三、命名管道(fifo)

1、命名管道的基本概念

2、命名管道的创建

3、实战演练

4、运行结果

四、补充

1、wait()函数

2、acess()函数

3、Linux下文件系统权限


一、简介

        本篇文章主要讲解Linux环境编程中进程间通信的两种常用方法:管道(Pipe)和命名管道(FIFO)。

管道:一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系(通常是指父子进程关系)的进程间使用;

 命名管道:也是一种半双工通信,但是它允许无亲缘关系进程间的通信;

        以上只是向大家简单介绍了管道与命名管道,具体的讲解请继续往下看:

二、管道(Pipe)

1、管道的基本概念

        管道是UNIX系统IPC的最古老的形式,它的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据。

        管道由两个文件描述符引用,一个表示读端,一个表示写端。管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺利地读取数据。

        该缓冲区可以看成一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,数据被读出后,就不会再存在于管道之中了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或者写进程是否进入等待队列,当空的缓冲区有新数据写入或者原来的缓冲区有数据读出时,就会唤醒等待队列中的进程继续读写。

2、管道的局限性

(1)半双工通信方式,数据只能在一个方向流动。

(2)管道只能在具有公共祖先之间的两个进程之间使用。

(3)数据一旦被读走,便不在管道中存在,不可反复读取。

(4)管道的缓冲区是有限的(管道存在于内存中,在管道创建时,为缓冲区分配一个页面大小)。

(5) 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须实现约定好数据的格式,比如多少字节算一个消息(或命令、或记录)等待;

3、管道的创建

        相信大家在接触Linux的时候都听过一句话——Linux下一切皆是文件!我们也可以把管道看做是一种特殊的文件,对于它的读写我们也可以调用write、read等函数。但是管道不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

        管道是通过调用pipe函数创建的,让我们先看一下函数原型吧:

#include    <unistd.h>

 

int   pipe(int   pipefd[2]);         //返回值:成功返回0,出错返回-1

经由参数fd返回的两个文件描述符:fd[0]为读而打开,fd[1]为写而打开

那创建管道,我们又如何能够实现进程之间通信呢?

        通常,我们的进程先调用pipe,接着调用fork,从而创建了父进程与子进程之间的IPC通道。调用fork之后,如图所示:

fork()之后做什么取决于我们想要的数据流的反向,共两种情况,让我们分别来看看:

(1)从父进程到子进程方向的管道

        即父写子读,关闭父进程管道的读端fd[0],再关闭子进程管道的写端fd[1],如图:

 (2)从子进程到父进程方向的管道

        即子写父读,关闭父进程管道的写端fd[1],再关闭子进程管道的读端fd[0],如图:

4、管道的读写规则

当没有数据可读时

(1)O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

(2)O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

当管道数据满的时候

(1)O_NONBLOCK disable: write调用阻塞,直到有进程读走数据。

(2)O_NONBLOCK enable:调用返回-1,errno值为EAGAIN。

关闭管道的一端

(1)当读一个写端被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束。

(2)当写一个读端被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1 。

5、实战演练

题目:

        编写一个程序,实现父进程给子进程方向发送数据。

思考过程:

        首先父进程调用pipe创建管道之后fork(),这时子进程会继承父进程所有打开的文件描述符(包括管道),这时对于一个管道就有四个读写端(父子进程各有一对管道读写端),如果需要父进程往子进程里写数据,则需要在父进程中关闭读端,在子进程中关闭写端;而如果需要子进程往父进程中写数据,则可以在父进程关闭写端,然后子进程中关闭读端。

代码如下:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MSG_STR         "This message is from parent:Hello,child process!"

int main(int argc,char *agv[])
{

        int             pipe_fd[2];
        int             rv;
        int             pid;
        char            buf[512];
        int             status;

        if(pipe(pipe_fd) < 0)
        {
                printf("Create pipe failure:%s\n",strerror(errno));
                return -1;
        }

        if((pid = fork()) < 0)
        {
                printf("Create child prcess failure:%s\n",strerror(errno));
                return -2;
        }

        else if(pid == 0)
        {
                /*child process close write ,then read data from parent process*/
                close(pipe_fd[1]);

                memset(buf,0,sizeof(buf));
                rv = read(pipe_fd[0],buf,sizeof(buf));
                if(rv < 0)
                {
                        printf("Child process read from pipe failure:%s\n",strerror(errno));
                        return -3;
                }

                printf("Child process read %d bytes data from pipe:[%s]\n",rv,buf);
                return 0;
        }

        /*parent process close read,then write data to child process*/
        close(pipe_fd[0]);
        if(write(pipe_fd[1],MSG_STR,strlen(MSG_STR)) < 0)
        {
                printf("Parent process write data to pipe failure:%s\n",strerror(errno));
                return -4;
        }

        printf("Parent start wait child process exit...\n");
        wait(&status);

        return 0;
}
                                     

运行结果:

三、命名管道(fifo)

1、命名管道的基本概念

        命名管道通信也属于进程间通信IPC的一种常见方式,他与管道的不同是:命名管道可以实现两个毫不相干的独立进程间通信。 

        FIFO不同于管道之处在于它提供一个路径与之相关联,以FIFO的文件形式存在于系统中。它在磁盘上有对应的节点,但是没有数据块。换句话说就是,命名管道只是拥有一个名字和相应的访问权限,并且命名管道的大小为0 ,命名管道就是一个内存文件。

       大家不用把命名管道想的很复杂,你就可以把它理解为在文件系统中挂了个名字,但也仅仅是个挂个名而已,没有实际的大小。目的就是能叫进程看到这个文件,因为文件系统中的文件都可以被进程看见。  

2、命名管道的创建

        我们可以通过调用mkfifo()来创建命名管道。一旦建立,任何进程都可以通过文件名将其打开和进行书写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍在。

        让我们先来看一下mkfifo()函数的原型吧:

#include <sys/types.h>
#include <sys/stat.h>

int  mkfifo(const  char *pathname, mode_t  mode);

参数说明:

(1)第一个参数pathname: 在这里既可以传绝对路径,也可以传相对路径(关于绝对路径与相对路径)。当然,绝对路径更灵活,但是也更长。

(2)第二个参数mode:其实就是创建命名管道时的初始权限,实际权限需要经过umask掩码进行计算。(关于文件权限我在文章最后给大家讲解,就不在这里赘述了)

3、实战演练

题目---实现进程间聊天:

        在程序中创建两个掩藏的命名管道文件(.fifo_chat1和.fifo_chat2)在不同的进程间进行通信。该程序需要运行两次(即两个进程),其中进程0(mode=0)从标准输入里读入数据后通过命名管道2(.fifo_chat2)写入数据给进程1(mode=1);而进程1(mode=1)则从标准输入里读入数据后通过命名管道1(.fifo_chat1)写给进程0。

        请利用命名管道编写一个程序来实现上述要求。

代码如下:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <libgen.h>
#include <sys/select.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FIFO_FILE1      ".fifo_chat1"
#define FIFO_FILE2      ".fifo_chat2"

int g_stop = 0;

void sig_pipe(int signum)
{
        if(SIGPIPE == signum)
        {
                printf("get pipe broken signal and let programe exit\n");
                g_stop = 1;
        }
}

int main(int argc,char *argv[])
{
        int             fdr_fifo;
        int             fdw_fifo;
        int             rv;
        fd_set          rdset;
        char            buf[1024];
        int             mode = -1;

        if(argc != 2)
        {
                printf("Usage:%s [0/1]\n",basename(argv[0]));
                printf("This chat program need run twice,1st time run with [0] and 2nd time with [1]\n");
                return -1;
        }
        mode = atoi(argv[1]);

        /*管道是一种半双工的通信方式,如果要实现两个进程间的双向通信则需要两个管道,即两个管道分别作为两个进程的读端和写端*/

        /*测试文件是否存在,不存在则建立*/
        if(access(FIFO_FILE1,F_OK))
        {
                printf("FIFO file \"%s\" not exist and create it now\n",FIFO_FILE1);
                mkfifo(FIFO_FILE1,0666);
        }

        if(access(FIFO_FILE2,F_OK))
        {
                printf("FIFO file \"%s\" not exist and create it now\n",FIFO_FILE2);
                mkfifo(FIFO_FILE2,0666);
        }

        signal(SIGPIPE,sig_pipe);

        if(0 == mode)
        {
                /*这里以只读模式打开命名管道FIFO_FILE1的读端,默认是阻塞模式;如果命名管道的写端打不开,则open()将会一直阻塞,所以另外一个进程必须首先以>
写模式打开该文件FIFO_FILE1,否则会出现死锁*/
                printf("start open '%s' for read and it will blocked untill write endpoint opened...\n",FIFO_FILE1);
                if((fdr_fifo = open(FIFO_FILE1,O_RDONLY)) < 0)
                {
                        printf("Open fifo[%s] for chat read endpoint failure:%s\n",FIFO_FILE1,strerror(errno));
                        return -1;
                }

                printf("start open '%s' for write...\n",FIFO_FILE2);
                if((fdw_fifo = open(FIFO_FILE2,O_WRONLY)) < 0)
                {
                        printf("Open fifo[%s] for chat write endpoint failure:%s\n",FIFO_FILE2,strerror(errno));
                        return -1;
                }
        }

         else
        {
                /*这里以只写模式打开命名管道FIFO_FILE1的写端,默认是阻塞模式;如果命名管道的读端不打开,则open()将会一直阻塞,因为前一个进程是先以读模式>
打开该管道文件的读端,所以这里必须先以写模式打开该文件的写端,否则将会出现死锁*/
                printf("start open '%s' for write and it will blocked untill read endpoint opened..\n",FIFO_FILE1);
                if((fdw_fifo = open(FIFO_FILE1,O_WRONLY)) < 0)
                {
                        printf("Open fifo[%s] for chat write endpoint failure:%s\n",FIFO_FILE1,strerror(errno));
                        return -1;
                }

                printf("start open '%s' for read...\n",FIFO_FILE2);
                if((fdr_fifo = open(FIFO_FILE2,O_RDONLY)) < 0)
                {
                        printf("Open fifo[%s] for chat read endpoint failure:%s\n",FIFO_FILE2,strerror(errno));
                        return -1;
                }
        }

        printf("start chating with another program now,please input message now:\n");
        while( !g_stop )
        {
                FD_ZERO(&rdset);
                FD_SET(STDIN_FILENO,&rdset);
                FD_SET(fdr_fifo,&rdset);

                /*select()多路复用监听标准输入和作为输入的命名管道读端*/
                rv = select(fdr_fifo+1,&rdset,NULL,NULL,NULL);
                if(rv < 0)
                {
                        printf("Select get timeout or error:%s\n",strerror(errno));
                        continue;
                }

                /*如果是作为输入的命名管道上有数据到来则从管道上读入数据并打印到标准输出上*/
                if(FD_ISSET(fdr_fifo,&rdset))
                {
                        memset(buf,0,sizeof(buf));
                        rv = read(fdr_fifo,buf,sizeof(buf));
                        if(rv < 0)
                        {
                                printf("read data from FIFO get error:%s\n",strerror(errno));
                                break;
                        }
                        else if(0 == rv)
                        {
                                printf("Another side of FIFO get closed and program will exit now\n");
                                break;
                        }

                        printf("<--%s",buf);
                }

                /*如果是作为输入上有数据到来,则从标准输入上读入数据后,将数据写入到作为输出的命名管道的另外一个进程*/
                if(FD_ISSET(STDIN_FILENO,&rdset))
                {
                        memset(buf,0,sizeof(buf));
                        fgets(buf,sizeof(buf),stdin);
                        write(fdw_fifo,buf,strlen(buf));
                }
        }
}

4、运行结果

./fifo_chat

 由图我们可以看图,我们仅仅输入一个参数是不行的。

./fifo_chat  0

./fifo_chat  1

         通过命名管道,我们成功实现了进程0和进程1聊天的程序。并且,我们也可以在文件系统中看到创建的两个掩藏的命名管道文件(.fifo_chat1和.fifo_chat2),并且大小为0 。

四、补充

        最后,就上文提到的一些知识点做一些简单的补充。

1、wait()函数

函数原型如下:

#include  <sys/types.h>
#include  <wait.h>

 int wait(int * status)

(1)函数功能:

         父进程一旦调用wait函数就立即开始阻塞,然后wait会分析当前进程的某个子进程是否已经退出,如果让它找到了这样一个退出的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回,如果没有找到,就一直阻塞,直至找到一个结束的子进程或接收到了一个指定的信号为止。

【注意:  当父进程忘记调用wait()等待已终止的子进程,子进程就会进入一种没有父进程的状态,此时子进程就是zombie(僵尸)进程。】

(2)参数status:

        用来保存被收集进程退出时的状态,它是一个指向int类型的指针,如果我们对这个子进程如何死掉的不在意,只想这把这个被僵尸进程消灭掉,就把这个参数置为NULL。如果status的值不是NULL,wait把子进程的退出状态取出并存入其中,这是一个整数值(int)。

2、acess()函数

函数原型如下:

#include    <unistd.h>

 
int access(const char * pathname, int mode)

参数说明:

(1)第一个参数pathname:需要检测的文件路径名。

(2)第二个参数mode:需要测试的操作模式。

mode说明如下:

R_OK              测试读许可权
W_OK              测试写许可权
X_OK              测试执行许可权
F_OK              测试文件是否存在 

3、Linux下文件系统权限

         对于一个文件来说能访问它的用户大致分为三种: 拥有者(owner)、所属组(grouper)、其他人(other)。

输入ls -al,出现如图所示的情况,第一列即为文件权限。

 接下来给大家简单讲解一下mkfifo()里mode值的计算:

第一个字母代表文件的类型,如“d”代表目录、“p”代表管道、“-”代表文件等等。

接下来共有九位,三位为一组,分别对应owner权限、grouper权限和other权限。r代表可读、w代表可写、x代表可执行(文件代表可执行、目录代表可进入),“-”则代表该成员没有相应的权限。

        接下来讲一下“421”法,“r”用4来表示,“w”用2来表示,“x”用1来表示。那举个例子,计算drwxrwxrx-就是(4+2+1)(4+2+1)(4+2+0),也就是该目录的权限为“0777”(第一位0表示特殊权限,暂时不用理会)。

文章到这里就结束了,如果哪里有问题,欢迎大家在评论区指出!

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

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

相关文章

深入理解stress/stress-ng

文章目录 一、概述二、安装2.1、源码编译安装2.2、命令行安装2.3、安装确认 三、重要参数详解3.1、查询支持的参数3.2、重要参数说明 四、实例4.1、压测CPU4.2、压测内存4.3、压测IO4.4、压测磁盘及IO4.5、压测磁盘及CPU 团队博客: 汽车电子社区 一、概述 stress是一种工作负载…

Java基础进阶03-注解和单元测试

目录 一、注解 1.概述 2.作用 3.自定义注解 &#xff08;1&#xff09;格式 &#xff08;2&#xff09;使用 &#xff08;3&#xff09;练习 4.元注解 &#xff08;1&#xff09;概述 &#xff08;2&#xff09;常见元注解 &#xff08;3&#xff09;Target &#x…

Codewave学习体验分享:低代码开发世界的黑马

前言 Codewave平台介绍 CodeWave智能低代码开发平台基于网易自研拥有大规模参数和深度学习能力的智能模型底座产品架构,为企业级应用提供更加智能化研发的软件生产方式,IT人员可以轻易实现从“智能生成”到“可视化拖拽调整”的全栈低代码应用搭建,让复杂应用开发更加高效,加快…

百度百科词条编辑规则是什么?

百度百科词条编辑规则是指在百度百科平台上编辑和创建词条时需要遵循的一系列标准和指南。百度百科作为全球最大的中文百科全书&#xff0c;旨在为用户提供准确、全面、客观的知识信息。为了确保词条内容的质量&#xff0c;百度设定了严格的编辑规则。伯乐网络传媒来给大家分享…

2 搭建模块环境

2.1 架构的问题分析 当前要开发的是媒资管理服务&#xff0c;目前为止共三个微服务&#xff1a;内容管理、系统管理、媒资管理&#xff0c;如下图&#xff1a; 后期还会添加更多的微服务&#xff0c;当前这种由前端直接请求微服务的方式存在弊端&#xff1a; 如果在前端对每…

电脑监控软件都有哪些,哪款好用 | 四款热门软件盘点

在信息化时代&#xff0c;电脑已经成为我们工作和生活中不可或缺的工具。然而&#xff0c;随着电脑使用的普及&#xff0c;也带来了一些安全和隐私方面的问题。 为了保护企业的机密资料和员工的行为规范&#xff0c;越来越多的企业开始使用电脑监控软件来加强管理和监控。 本…

常见问题-d3dx9_39.dll丢失如何解决,快速修复d3dx9_39.dll丢失教程

d3dx9_39.dll 是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;它是微软 DirectX 组件的一部分&#xff0c;主要用于支持 3D 图形和声音处理功能。这个文件通常在运行需要 DirectX 支持的游戏或应用程序时被调用。 一、d3dx9_39.dll文件属性 以下是 d3dx9_39.dl…

如何解决Flutter应用程序的兼容性问题

随着移动应用开发领域的不断发展&#xff0c;Flutter作为一种跨平台框架&#xff0c;受到了越来越多开发者的青睐。要确保Flutter应用程序能够在不同的设备和操作系统上稳定运行&#xff0c;并提供一致的用户体验&#xff0c;我们需要重视应用程序的兼容性问题。下面将简单的介…

「阿里云」幻兽帕鲁个人服务器已上线,3分钟快速搭建

基于阿里云搭建幻兽帕鲁服务器方法&#xff0c;1到2分钟部署完成&#xff0c;稳定运行无卡顿&#xff0c;阿里云服务器网aliyunfuwuqi.com分享保姆级手把手教程&#xff0c;基于阿里云计算巢、云服务器或无影云桌面都可以&#xff1a; 基于阿里云幻兽帕鲁服务器创建教程 基于…

在线教育SSR网站项目

Nuxt3 Vue3开发的在线教育SSR 一、首页 二、考试页 三、拼团 四、秒杀 五、直播 六、专栏 七、电子书 八、社区 九、课程 十、用户中心

idea快速解决jar包重复导入或冲突

1、下载 Maven Helper 插件 File -> settings -> plugins 2、启动项目&#xff0c;报jar重复导入或者冲突 3、找到需要解决冲突的pom文件&#xff0c;点击Dependency Analyzer

Go 从标准输入读取数据

fmt.Scan系列 fmt.Scan函数定义如下&#xff1a; // Scan scans text read from standard input, storing successive space-separated values into successive arguments. // Newlines count as space. // It returns the number of items successfully scanned. // If tha…

JavaWeb基础01-基本技术体系介绍和相关工具的安装

一、JavaWeb 1.概述 Web&#xff1a;全球广域网&#xff0c;也称为万维网(www)&#xff0c;能够通过浏览器访问的网站JavaWeb&#xff1a;是用Java技术来解决相关web互联网领域的技术栈 2.组成 &#xff08;1&#xff09;网页&#xff1a;展示数据&#xff08;前端技术&…

服务器是什么?(四种服务器类型)

服务器 服务器定义广义: 专门给其他机器提供服务的计算机。狭义:一台高性能的计算机&#xff0c;通过网络提供外部计算机一些业务服务 个人PC内存大概8G&#xff0c;服务器内存128G起步 服务器是什么 服务器指的是 网络中能对其他机器提供某些服务的计算机系统 &#xff0c;相对…

蓝桥杯备战——4.继电器/蜂鸣器

1.分析原理图 最好自己先去查查138以及ULN2003的使用方法&#xff0c;我这里直接讲思路。 由上图我们可以看到如果138输入ABC101,则输出Y50,此时若WR通过跳线帽接地则Y5C1 &#xff0c;于是573(U9)处于输出跟随输入P0状态&#xff0c;此时若P061&#xff0c;则573输出Q71&am…

有关链表的题目

目录 1.环形链表的约瑟夫问题 2.链表的中间节点 3.合并两个有序链表 4.反转链表 5.移除链表元素 1.环形链表的约瑟夫问题 环形链表的约瑟夫问题_牛客题霸_牛客网 (nowcoder.com) 思路&#xff1a;题目给出结构是环形链表&#xff0c;且题目已经定义好了环形链表的结构。 1…

无线充电 发射端(3)

终于可以传输功率了&#xff01;文末会附上这几个章节的全景图&#xff0c;归纳无线充电发射端状态切换。 这部分涉及到的报头 - 1/控制错误数据包&#xff1b;2/整流功率数据包&#xff1b;3/充电状态数据包&#xff1b;4/结束功率传输数据包&#xff1b;5/专有数据包&#…

Oracle触发器简单应用示例(销售与库存)

目录 一、应用描述 1、应用场景&#xff1a; 2、具体场景&#xff1a; 二、表结构介绍 1、表名介绍&#xff1a; 2、表结构&#xff1a; 三、设置触发器 四、运行示例 1、初始库存描述 2、有库存情况 2.1 1001号产品售出1件 2.2 1001号产品库存已减1 3、无库存情况…

Contest3388 - 2024寒假集训-排位赛竞 赛(二)-补题(A-M)

问题 A: 三五倍数(问题 A: 三五倍数 - BUCTOJ) 思路&#xff1a;这题就暴力&#xff0c;注意一下是小于1000&#xff0c;别取到1000就行。 #include<bits/stdc.h> using namespace std; int main() {int sum0;for(int i3;i<1000;i){if(i%30||i%50) sumi;}cout<<…

线性代数基础【6】二次型

第一节、二次型的基本概念及其标准型 一、基本概念 ①二次型 含n个变量x1,x2,…,xn,且每项都是2次的齐次多项式 ②标准二次型 只含有平方项不含交叉项的二次型称为标准二次型 ③二次型的标准化 设f(X)X^TAX 为一个二次型,经过可逆的线性变换XPY(即P为可逆矩阵)把二次型…