Linux系统编程2:管道

Linux系统编程2:管道_第1张图片
图片来自网络

1. 简介

  • 查看管道命令:man 7 pipe

分类

  • 匿名管道
  • FIFO管道/命名管道

2 匿名管道

2.1 单工管道

程序进程与Shell命令行进程单项通信。

① 打开管道FILE* popen (const char *command, const char *open_mode)

  • 参数
No. 参数 含义
1 command 命令行字符串
2 open_mode "r"只读"w"只写
  • 返回值
No. 返回值 含义
1 NULL 文件描述符
2 NULL 打开失败

② 读取size_t fread ( void *buffer, size_t size, size_t count, FILE *stream)

  • 参数
No. 参数 含义
1 buffer 用于接收数据的内存地址
2 size 读取每个数据项的字节数
3 count 数据项个数
4 stream 输入流
  • 返回值
No. 返回值 含义
1 >count 出错
2 正数 真实读取的数据项个数

③ 写入size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream)

  • 参数
No. 参数 含义
1 buffer 写入数据的内存地址
2 size 写入数据项的字节数
3 count 写入数据项的个数
4 stream 目标文件指针
  • 返回值
No. 返回值 含义
1 >count 出错
2 正数 真实写入的数据项个数

④ 关闭管道int pclose(FILE *stream);

  • 参数
No. 参数 含义
1 stream 文件描述符

  • 返回值
No. 返回值 含义
1 -1 失败
2 0 成功

示例

  • write
#include 
#include 
#include 
 
int main(){
    FILE* fd = popen("wc","w");
    //FILE* fd = popen("ls -l","r");
    //char str[] = "123 456";       
    char str[] = "123 456\n";       
    size_t n = fwrite(str,sizeof(char),sizeof(str),fd);
    if(n > sizeof(str)){
        fprintf(stderr,"FILE:%d,LINE:%d-fwrite error",__FILE__,__LINE__);
        exit(EXIT_FAILURE);
    }
    pclose(fd);
}
  • read
#include 
#include 
#include 
 
int main(){
    FILE* fd = popen("ps -ef","r");
    //FILE* fd = popen("ls -l","r");
         
    char buf[BUFSIZ];
    size_t count = 0;
    printf("read data:\n");
    do{
        memset(buf,'\0',BUFSIZ);
        size_t n = fread(buf,sizeof(char),BUFSIZ-1,fd);
        if( n > BUFSIZ - 1 ){
            perror("fread error");
            exit(EXIT_FAILURE);
        }
        count += n;
        printf("\n%d:\n%s",n,buf);
    }while(!feof(fd));
    printf("total size:%ld\n",count);
    pclose(fd);
}

本质

  • 启动shell和命令两个进程,从命令进程中读/写文件流。
  • 解决exec和system无法返回输出数据问题

特点

  • 方便使用系统自带功能,并且可以执行比较复杂Shell
  • 默认启动两个进程,效率较低。
操作 管道 文件
打开 popen() fopen()
关闭 pclose() fclose()

2.2 半双工管道

① 创建管道int pipe(int filedes[2])

  • 参数
No. 参数 含义
1 filedes[0]
2 filedes[1]
  • 返回值
No. 返回值 含义
1 -1 失败
2 0 成功

② 读取ssize_t write(int fd, const void *buf, size_t nbyte)

  • 参数
No. 参数 含义
1 fd 文件描述符
2 buf 写入数据的内存单元
3 nbyte 写入文件指定的字节数
  • 返回值
No. 返回值 含义
1 -1 出错
2 正数 写入的字节数

③ 写入ssize_t read(int fd, void *buf, size_t count)

  • 参数
No. 参数 含义
1 fd 文件描述符
2 buf 读取数据的内存单元
  • 返回值
No. 返回值 含义
1 -1 出错
2 0 无数据
3 正数 读取的字节数

④ 控制int fcntl(int fd, int cmd, long arg)

如果管道是空的,read()默认是阻塞

  • 参数
No. 参数 含义
1 fd 文件描述符
2 cmd F_GETFL:获取文件描述符状态;F_SETFL:设置文件描述符状态;
3 arg O_NONBLOCK:非阻塞;O_BLOCK:阻塞

把文件描述符改为非阻塞的fcntl(filedes,F_SETFL,O_NONBLOCK);

⑤ 关闭管道close(filedes)

示例

  • 单进程读写管道
#include 
#include 
#include 
 
int main(){
    int fd[2];
    pipe(fd);
    char in[] = "Hello pipe";
    write(fd[1],in,sizeof(in));
    printf("write:%s\n",in);
 
    char out[sizeof(in)]={0};
    ssize_t n = read(fd[0],out,sizeof(out));
    if(-1 == n){
        perror("read error");
        return -1;
    }
    printf("read:%s\n",out);
 
    close(fd[0]);
    close(fd[1]);
}
  • 两进程读写
#include 
#include 
#include 
 
int main(){
    int fd[2];
    pipe(fd);
    if(!fork()){// child
        char in[] = "Hello pipe";
        write(fd[1],in,sizeof(in));
        printf("child %d write:%s\n",getpid(),in);
    }else{// parent
        char out[BUFSIZ]={0};
        ssize_t n = read(fd[0],out,sizeof(out));
        if(-1 == n){
            perror("read error");
            return -1;
        }
        printf("parent %d read:%s\n",getpid(),out);
    }
    close(fd[0]);
    close(fd[1]);
}
  • 两进程先读后写
#include 
#include 
#include 
 
int main(){
    int fd[2];
    pipe(fd);
    if(!fork()){// child
        char in[] = "Hello pipe";
        sleep(3);
        write(fd[1],in,sizeof(in));
        printf("child %d write:%s\n",getpid(),in);
    }else{// parent
        char out[BUFSIZ]={0};
        ssize_t n = read(fd[0],out,sizeof(out));
        if(-1 == n){
            perror("read error");
            return -1;
        }
        printf("parent %d read:%s\n",getpid(),out);
    }
    close(fd[0]);
    close(fd[1]);
}
  • 两进程先读后写(非阻塞)
#include 
#include 
#include 
#include 
 
int main(){
    int fd[2];
    pipe(fd);
    if(!fork()){// child
        char in[] = "Hello pipe";
        sleep(3);
        write(fd[1],in,sizeof(in));
        printf("child %d write:%s\n",getpid(),in);
    }else{// parent
        fcntl(fd[0],F_SETFL,O_NONBLOCK);
        char out[BUFSIZ]={0};
        ssize_t n = read(fd[0],out,sizeof(out));
        if(-1 == n){
            perror("read error");
            return -1;
        }
        printf("parent %d read:%s\n",getpid(),out);
    }
    close(fd[0]);
    close(fd[1]);
}
  • 更加规范的写法(关闭不需要的管道)
#include 
#include 
#include 
#include 
 
int main(){
    int fd[2];
    pipe(fd);
    if(!fork()){// child
        close(fd[0]);
        char in[] = "Hello pipe";
        sleep(3);
        write(fd[1],in,sizeof(in));
        printf("child %d write:%s\n",getpid(),in);
        close(fd[1]);
    }else{// parent
        close(fd[1]);
        fcntl(fd[0],F_SETFL,O_NONBLOCK);
        char out[BUFSIZ]={0};
        ssize_t n = read(fd[0],out,sizeof(out));
        if(-1 == n){
            perror("read error");
            return -1;
        }
        printf("parent %d read:%s\n",getpid(),out);
        close(fd[0]);
    }
}

本质

文件描述符

文件描述符 文件流
数据 int整数 FILE指针
标准 POSIX ANSI C
打开 open fopen
关闭 close fclose
read fread
write fwrite
定位 lseek fseek
  • 文件流是文件描述符之上的封装。文件流通过增加缓冲区减少读写系统调用次数来提高读写效率。在进程的用户空间封装的FILE结构,以提高可移植性和效率。

2.3 管道复制

分类 文件描述符 文件号
标准输入 STDIN_FILENO 0
标准输出 STDOUT_FILENO 1
标准出错信息 STDERR_FILENO 2

内核为每个进程创建的文件描述符。

① 函数int dup(int oldfd)

  • 参数
No. 参数 含义
1 oldfd 旧文件描述符
  • 返回值
No. 返回值 含义
1 -1 失败
2 其他 新文件描述符

② 函数int dup2(int oldfd, int newfd)

  • 参数
No. 参数 含义
1 oldfd 旧文件描述符
2 newfd 新文件描述符
  • 返回值
No. 返回值 含义
1 -1 失败
2 其他 最小及尚未使用的文件描述符

示例

  • 复制标准输出
    新文件描述符与旧文件描述符不同,但是具备旧文件描述符功能
#include 
#include 
#include 
#include 
 
int main(){
    int fd = dup(STDOUT_FILENO);
    fprintf(fdopen(fd,"w"),"%d printf:Hello dup\n",fd);
}
  • 复制文件描述符
    新文件描述符与旧文件描述符不同,但是具备旧文件描述符功能
#include 
#include 
#include 
#include 
#include 
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
 
int main(){
    int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE); 
    char str[]="Hello dup\n";
    write(fd,str,sizeof(str));
    int cp_fd = dup(fd);
    printf("copy %d to %d",fd,cp_fd);
    write(cp_fd,str,sizeof(str));
    //fprintf(fdopen(fd,"w"),"%d printf:Hello dup\n",fd);
    close(fd);
}
  • 把文件描述符重定向(复制)到标准输出
    printf()直接输出到文件中,不再输出到终端
#include 
#include 
#include 
#include 
#include 
 
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
 
int main(){
    int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE); 
    char str[]="Hello dup\n";
    dup2(fd,STDOUT_FILENO);
    printf("%d printf:Hello dup\n",fd);
}
  • 把文件描述符重定向(复制)到标准输出,并且输出后还原
    注意把标准输出流从文件重定向(复制)回终端,需要清除缓冲区。
#include 
#include 
#include 
#include 
#include 
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
    int save_fd = dup(STDOUT_FILENO);
    int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE); 
    if(-1 == dup2(fd,STDOUT_FILENO)){
        perror("dup2 error0");
        return 1;
    }
    close(fd);
    printf("%d printf:Hello dup\n",fd);
    fflush(stdout);// 一定要清除缓冲区,否则会输出到终端
    if(-1 == dup2(save_fd,STDOUT_FILENO)){
        perror("dup2 error");
        return 1;
    }
    close(save_fd);
    printf("%d printf:this is save\n",save_fd);
}

特点

必须是亲缘进程之间

案例

  • ps_self打印自身的父子进程信息
  • ps_other执行参数中的命令,并且打印参数中的命令相关的父子进程

2.4 FIFO管道/命名管道

① 创建命名管道int mkfifo(pathname,mode)

  • 参数
No. 参数 含义
1 pathname 文件路径,文件必须不存在
2 mode 模式
  • 返回值
No. 返回值 含义
1 0 成功
2 非零 失败

② 打开FIFO文件int open(const char *path, int mode)

  • 参数
No. 参数 含义
1 pathname 文件路径
2 mode 模式
  • 模式
No. 模式 含义
1 O_RDONLY 阻塞只读
2 O_RDONLY | O_NONBLOCK 非阻塞只读
3 O_WRONLY 阻塞只写
4 O_WRONLY | O_NONBLOCK 非阻塞只写
  • 返回值
No. 返回值 含义
1 -1 失败
2 其他 文件描述符

特点

  • 可以是非亲缘进程之间
  • 读写必须同时执行,否则阻塞。

案例

  • FIFO工具箱

3. 通信分类

只写单工
只读单工
半双工
全双工
No. 类型 创建/打开 关闭
1 单工 popen() pclose read() write()
2 半双工 pipe()/open() close() read() write()
3 FIFO半双工 mkfifo()/open close()/unlink() read() write()
4 全双工 socketpair() close() read() write()

3.1 单进程管道

Linux系统编程2:管道_第2张图片
单进程管道

管道通常用于进程间通信

3.2 父子进程单向管道

3.3.1 概念图解

Linux系统编程2:管道_第3张图片
父子进程管道
Linux系统编程2:管道_第4张图片
父进程关闭fd[0] 子进程关闭fd[1]
Linux系统编程2:管道_第5张图片
父子进程单向管道

3.3.2 原理图解

Linux系统编程2:管道_第6张图片
Linux系统编程2:管道_第7张图片
Linux系统编程2:管道_第8张图片

3.3 父子进程双向管道

Linux系统编程2:管道_第9张图片
父子进程双向管道

文件描述符

Linux内核使用三个关联的数据结构,表示打开的文件。

Linux系统编程2:管道_第10张图片
Linux系统编程2:管道_第11张图片
  • 打开不同的文件


    Linux系统编程2:管道_第12张图片
  • 打开相同的文件


    Linux系统编程2:管道_第13张图片
  • 父子进程共享文件


    Linux系统编程2:管道_第14张图片

dup()/dup2()原理图

Linux系统编程2:管道_第15张图片

你可能感兴趣的:(Linux系统编程2:管道)