8、管道 - 看这一篇就够了

八、管道

1、什么是管道

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个"管道"
  • 我们通常把是把一个进程的输出连接或“管接”(经过管道来连接)到另一个进程的输入。

2、在shell中使用管道

  • 链接shell命令:把一个进程的输出直接馈入另一个的输入,命令格式如下

    cmd1 | cmd2

#生成一个8位的随机密码
tr -dc A-Za-z0-9_ </dev/urandom | head -c 8 | xargs

#查看系统中所有的用户名称,并按字母排序
awk -F: '{print $1}' /etc/passwd | sort

#列出当前用户使用最多的5个命令(print的列数根据实际情况而定)
history | awk '{print $2}' | sort | uniq -u | sort -rn | head -5

#查看系统中有哪些用户的登陆shell时/bin/bash
cat /etc/passwd | grep "/bin/bash" | cut -d: -f1,6   
#cut -d: -f1,6 表示以:为分隔符显示第1和第6列的内容-d指定分隔符,-f指定列

#查看当前目录的子目录个数
ls -l | cut -c 1 | grep "d" | wc -l
#ls -l  长格式列出当前目录的所有内容,每行的第一个字符表示文件的类
#cut -c 1 截取每行的第一个字符
#grep "d" 获取文件类型是目录的行
#wc -l  统计grep命令输出的行数,即子目录个数

#合并两个文件的内容
cat 1.txt | paste -d: 2.txt -
#paste -d: 2.txt - 表示以:为分割符合并两个文件,合并时2.txt文件的内容在前 -代表1.txt文件

3、管道特点

  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
  • 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

4、pipe函数

  • 功能:创建一无名管道
  • 原型:
#include <unistd.h>

int pipe(int pipefd[2]);
  • 参数:

    • pipefd[0]:表示读端
    • pipefd[1]:表示写端
  • 返回值:成功返回0,失败返回错误代码

示例

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

int main()
{
    int pipefd[2];
    char buf[20];
    if(pipe(pipefd) == -1){
        printf("error\n");
    }
    int fd = fork();
    if(fd == 0){
        read(pipefd[0],buf,20);
        printf("%s\n",buf);
        close(pipefd[0]);
        close(pipefd[1]);
    }else if(fd > 0){
        write(pipefd[1],"hello world",20);
        close(pipefd[0]);
        close(pipefd[1]);
    }else{
        printf("error\n");
    }
    return 0;
}

5、管道读写规则

  • 如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生

  • 当没有数据可读时,read调用就会阻塞,即进程暂停执行,一直等到有数据来到为止。

  • 如果管道的另一端已经被关闭,也就是没有进程打开这个管道并向它写数据时,read调用就会阻塞

  • 如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;

  • 当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数

  • 向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。

6、复制文件描述符dup、dup2 、fcntl

  • 复制文件描述符可以有三种办法:
    • dup、dup2 、fcntl
  • dup系统调用从头开始搜文件描述符数组,并且在找到第一个空闲文件描述符时完成它的复制。
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd,int newfd);//dup2系统调用复制操作之前,如果newfd已被打开,先关闭。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KcFI94rg-1589279199747)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200512001427990.png)]

fcntl()函数

  • 功能:实现对文件描述符的多种控制
  • 原型:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int oldfd,F_DUPFD,int minifd);
  • 参数:

    • minifd:从指定的minifd开始搜索空闲文件描述符,找到时,复制oldfd,返回值为新的文件描述符。

    • oldfd:要被复制的文件描述符

说明:另外fcntl还有其他的作用。

dup示例

//实现cat /etc/passwd | wc -l

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int pipefd[2];
    if(pipe(pipefd) == -1){
        fprintf(stderr,"error creating pipe\n");
        exit(1);
    }
    int fd = fork();
    if(fd == 0){
        close(1);//关闭标准输出
        dup(pipefd[1]);
        close(pipefd[1]);
        close(pipefd[0]);
        execlp("cat","cat","/etc/passwd",0);
        fprintf(stderr,"error trying to exec ls\n");
		sleep(1);
        exit(1);
    }else if(fd > 0){
        close(0);//关闭标准输入
        dup(pipefd[0]);
        close(pipefd[0]);
        close(pipefd[1]);
        execlp("wc","wc","-l",0);
        fprintf(stderr,"error trying to exec wc\n");
		exit(1);
    }else{
        fprintf(stderr,"error fork\n");
    }
    return 0;
}

7、popen函数

  • 作用:允许一个程序把另外一个程序当作一个新的进程来启动,并能对它发送数据或接收数据
FILE* popen(const char *command,const char *open_mode);
  • 参数:

    • command:待运行程序的名字和相应的参数
    • open_mode:必须是“r”或“w”
  • 如果操作失败,popen会返回一个空指针

  • 每个popen调用都必须指定“r”或“w”,在popen的标准实现里不支持任何其他的选项

  • 如果open_mode是“r”,调用者程序利用popen返回的那个“FILE*”类型的指针用一般的stdio库函数(比如fread)就可以读这个文件流

  • 如果open_mode是“w”,调用者程序就可以使用fwrite向被调用命令发送数据

pclose函数

  • 作用:关闭popen打开的与之关联的文件流
int pclose(FILE *stream_to_close);
  • pclose调用只有在popen启动的进程结束之后才能返回。
  • 如果在调用pclose的时候它仍然在运行,pclose将等待该进程的结束

示例

//通过popen实现cat /etc/passwd | wc -l

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

int main()
{
    FILE *read_fp;
    FILE *write_fp;
    char buffer[BUFSIZ + 1];//BUFSIZ的值等于一个常量值,这个值是8192,一般是会用来做数组的长度。
    int chars_read;
    memset(buffer,0,sizeof(buffer));
    read_fp = popen("cat /etc/passwd","r");
    if(read_fp != NULL){
        chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp);
        pclose(read_fp);
        if(chars_read > 0){
            write_fp = popen("wc -l","w");
            if(write_fp != NULL){
                fwrite(buffer,sizeof(char),strlen(buffer),write_fp);
				pclose(write_fp);
				exit(EXIT_SUCCESS);
            }
        }
    }
    exit(EXIT_FAILURE);
    return 0;
}

8、命名管道:FIFO文件

  • 管道应用的一个限制就是只能在相关的程序之间进行,这些程序是由一个共同的祖先进程启动的。
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件

创建一个命名管道

  • 命名管道可以从命令行上创建,推荐的命令行方法是使用下面这个命令:
    $ mkfifo filename
  • 命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);
int mknod(const char *filename,mode_t mode | S_IFIFO,(dev_t) 0);

用mkfifo创建一个命名管道

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
    int res = mkfifo("/tmp/my_fifo",0777);
    if(res == 0)
        printf("FIFO created\n");
    exit(EXIT_SUCCESS);
    return 0;
}

9、命名管道(FIFO文件)的打开规则

访问一个FIFO文件

  • FIFO和通过pipe调用创建的管道不同,它是一个有名字的文件而表示一个打开的文件描述符。
  • 在对它进行读或写操作之前必须先打开它
  • FIFO文件也要用open和close函数来打开或关闭,除了一些额外的功能外,整个操作过程与我们前面介绍的文件操作是一样的
  • 传递给open调用的是一个FIFO文件的路径名,而不是一个正常文件的路径名

用open打开FIFO文件

  • 在打开FIFO文件时需要注意一个问题:即程序不能以O_RDWR模式打开FIFO文件进行读写
  • 如果确实需要在程序之间双向传递数据的话,我们可以同时使用一对FIFO或管道,一个方向配一个;还可以用先关闭再重新打开FIFO的办法明确地改变数据流的方向

命名管道的打开规则

  • 如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志,即只设置了O_RDONLY),反之,如果当前打开操作没有设置了非阻塞标志,即O_NONBLOCK,则返回成功
  • 如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。

10、命名管道(FIFO文件)读写规则

  • 对于没有设置阻塞标志的写操作:
    • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回
    • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写。

示例

  • 先在终端:mkfifo myfifo
//fifo_r.cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int fd = open("myfifo",O_RDONLY);
	if(fd == -1){
		perror("open fifo");
		exit(1);
	}
	char buf[1024]={0};
    int fd_r;
	while(1){
        memset(&buf,0,sizeof(buf));
		fd_r = read(fd,&buf,sizeof(buf));
		if(fd_r == -1){
			perror("read fifo");
		}
		printf("read:%s\n",buf);
        sleep(1);
	}
	return 0;
}
//fifo_w.cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#define MYFIFO "./myfifo"

int main()
{
    if(access(MYFIFO,F_OK) == -1){//检查调用进程是否可以访问文件路径名
        int res = mkfifo(MYFIFO,0777);
        if(res < 0)
            exit(EXIT_FAILURE);
    }
	int fd = open(MYFIFO,O_WRONLY);
	if(fd == -1){
		perror("open fifo");
		exit(1);
	}
	char buf[1024]="hello world!";
    int fd_r;
	while(1){
		fd_r = write(fd,&buf,sizeof(buf));
		if(fd_r == -1){
			perror("write fifo");
		}
		printf("write:%s\n",buf);
        sleep(1);
	}
	return 0;
}
,0777);
        if(res < 0)
            exit(EXIT_FAILURE);
    }
	int fd = open(MYFIFO,O_WRONLY);
	if(fd == -1){
		perror("open fifo");
		exit(1);
	}
	char buf[1024]="hello world!";
    int fd_r;
	while(1){
		fd_r = write(fd,&buf,sizeof(buf));
		if(fd_r == -1){
			perror("write fifo");
		}
		printf("write:%s\n",buf);
        sleep(1);
	}
	return 0;
}

如果我的文章能够帮到您,可以点个赞!
您的每次 点赞、关注、收藏 都是对我最大的鼓励!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值