目录
八、管道
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已被打开,先关闭。
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;
}
如果我的文章能够帮到您,可以点个赞!
您的每次 点赞、关注、收藏 都是对我最大的鼓励!