目录

1.管道概念

2.管道分类

        1.匿名管道

        1.基本实现与概念

        2.站在文件描述符角度-深度理解管道

        3.站在内核角度-管道本质

        4.管道读写规则

        5.管道属性设置与阻塞验证       

         6.管道特点(匿名)

        2.命名管道

        1.创建一个命名管道

        2.命名管道的打开规则 

 3.匿名管道与命名管道的区别


1.管道概念

        管道是Linux中的最古老的通信方式;

        我们把一个进程链接到另一个进程的一个数据流称为一个"管道";

2.管道分类

        1.匿名管道

        1.基本实现与概念

        头文件: #include <unistd.h>

        功能:创建一无名管道原型int pipe(int fd[2]);

        参数fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端返回值:成功返回0,失败返回错误代码

         通常用fork函数来共享管道;接下来我们来实现一下匿名管道的基本读写的实现:

                                                                不关闭描述符

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>

int main(){
    int fd[2];
    int ret=pipe(fd);
    if(ret<0){
        perror("pipe");
        return 0;
    }
    pid_t pid=fork();
    if(pid<0){
        perror("fork");
        return 0;
    }else if(pid==0){
        //child
        //子进程进行接受
        char buf_read[1024]={0};
        read(fd[0],buf_read,sizeof(buf_read)-1);
        printf("The content is :%s\n",buf_read);
    }else{
        //father
        //父进程发送数据
        char buf_write[]="success!";
        write(fd[1],buf_write,strlen(buf_write));
        wait(NULL);
    }
    return 0;
}

                                                                运行结果

        我们可以看出来在父进程向管道写入信息,在子进程读取管道信息. 

                                                          关闭描述符

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>

int main(){
    int fd[2];
    int ret=pipe(fd);
    if(ret<0){
        perror("pipe");
        return 0;
    }
    pid_t pid=fork();
    if(pid<0){
        perror("fork");
        return 0;
    }else if(pid==0){
        //child
        //子进程进行接受
        close(fd[1]);
        char buf_read[1024]={0};
        read(fd[0],buf_read,sizeof(buf_read)-1);
        printf("The content is :%s\n",buf_read);
    }else{
        //father
        //父进程发送数据
        close(fd[0]);
        char buf_write[]="success!";
        write(fd[1],buf_write,strlen(buf_write));
        wait(NULL);
    }
    return 0;
}

                                                        运行结果 

                我们可以看出依旧可以进行读写~原因是什么呢?

        2.站在文件描述符角度-深度理解管道

 

        首先父进程创建管道,系统默认打开三个文件:标准输入,标准输出,和标准错误;创建管道则有fd[0],fd[1];分别表示管道的读端和写端;

         然后父进程创建子进程,子进程拷贝父进程,也拷贝了管道信息;所以父子进程共享管道;

         父进程关闭读端,子进程关闭写端,但是管道是共享的,依旧可以进行信息交互;

        3.站在内核角度-管道本质

 看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。

        4.管道读写规则

        当没有数据可读时O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

        当管道满的时候O_NONBLOCK disable: write调用阻塞,直到有进程读走数据O_NONBLOCK enable:调用返回-1,errno值为EAGAIN如果所有管道写端对应的文件描述符被关闭,则read返回0   

        如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出

        当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

        当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

        5.管道属性设置与阻塞验证       

        fcntl函数原型:

#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);

 (1)F_DUPFD

与dup函数功能一样,复制由fd指向的文件描述符,调用成功后返回新的文件描述符,与旧的文件描述符共同指向同一个文件。

(2)F_GETFD

读取文件描述符close-on-exec标志

(3)F_SETFD

将文件描述符close-on-exec标志设置为第三个参数arg的最后一位

(4)F_GETFL

获取文件打开方式的标志,标志值含义与open调用一致

(5)F_SETF

设置文件打开方式为arg指定方式

设置读端为非阻塞:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
    int fd[2];
    int ret=pipe(fd);
    if(ret<0){
        perror("pipe");
        return 0;
    }
    pid_t pid=fork();
    if(pid<0){
        perror("fork");
        return 0;
    }else if(pid==0){
        //child 
        //关闭写端
        //设置读端为非阻塞属性
        //读
        close(fd[1]);
        int flag=fcntl(fd[0],F_GETFL);
        fcntl(fd[0],F_SETFL,flag|O_NONBLOCK);
        char buf_read[1024]={0};
        ssize_t read_size=read(fd[0],buf_read,sizeof(buf_read)-1);
        printf("read_size:%ld\n",read_size);
        perror("read");

    }else{
        //father
        //关闭读端
        //写关闭/不关闭
        close(fd[0]);
        close(fd[1]);
    }
    while(1){
        sleep(1);
    }
    return 0;
}

                                                                        运行结果为:

 设置写端非阻塞:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
    int fd[2];
    int ret=pipe(fd);
    if(ret<0){
        perror("pipe");
        return 0;
    }
    pid_t pid=fork();
    if(pid<0){
        perror("fork");
        return 0;
    }else if(pid==0){
        //child
        //关闭子进程读端
        //设置子进程写段为非阻塞
        //子进程写
        close(fd[0]);
        int flag=fcntl(fd[1],F_GETFL);
        fcntl(fd[1],F_SETFL,flag|O_NONBLOCK);
        ssize_t write_size;
        while(1){
            write_size=write(fd[1],"a",1);
            if(write_size<0){
                break;
            }
        }
        printf("write_size:%ld\n",write_size);
    }else{
        //father
        //关闭父进程写端
        //关闭父进程读端/不关闭
        close(fd[1]);
        //close(fd[0]);
        while(1){
            sleep(1);
        }
    }
    return 0;
}

                                                运行结果:

         6.管道特点(匿名)

        只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;

        通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

        管道提供流式服务一般而言,进程退出,管道释放,所以管道的生命周期随进程一般而言,内核会对管道操作进行同步与互斥管道是半双工的;

        数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;

        2.命名管道

        管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

        如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

        命名管道是一种特殊类型的文件

        1.创建一个命名管道

$ mkfifo pipe1

执行结果: 

也可以程序创建

函数原型:

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

                                        代码如下: 

#include <stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(){
    mkfifo("pipe2",0664);
    return 0;
}

                                        运行结果

        2.命名管道的打开规则 

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

        O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO

        O_NONBLOCK enable:立刻返回成功如果当前打开操作是为写而打开FIFO时

        O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO 

        O_NONBLOCK enable:立刻返回失败,错误码为ENXIO                

 3.匿名管道与命名管道的区别

        匿名管道由pipe函数创建并打开。

        命名管道由mkfifo函数创建,打开用openFIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐