管道命令(pipe)
首先提到管道,大家都不会太陌生,生活中也处处可见管道,如自来水管道,天然气管道,无疑它们的作用就是作为两个事物的中间桥梁,起着导通的作用,那么在Linux中亦是如此。
Linux命令行中,我们常使用“ | ”这个符号,因为管道的作用就是作为数据之间的导向,即大多数情况将一个进程的输出导向另外一个进程的输入。
如查看/etc下面的文件
ls -al /etc
但结果是/etc下的文件太多了,而使得我们无法正常浏览和获取到有用的讯息,那么可以试图将查询的结果放入”more”或者“less”(都用来显示文件,所不同的是less不但支持下翻页,并且支持上翻页)。
ls -al /etc | less
由此我们可以看出管道接受来自前面命令得到的数据,然后传递给后面的命令
通常和管道密切联系的命令:
选取命令:cut,grep
cut,即从表意可以看出切的意思,就是将一段数据中的某一小段切除出来。
-d:后面接分隔字符,与-f连用
-f:通过-d切分为数段后,选取具体某一段
-c:以字符为单位,取出固定的区间
举个例子即可通晓其作用:
ll | cut -d ' ' -f 10
先显示主工作目录下的文件信息,在通过空格为界限分为10段,取最后一段的文件名。
grep:分析一行数据,选取出我们需要的信息
-a:将二进制文件以test文件方式查找
-c:计算找到查找字符串的次数
-i:不区分大小写
-n:顺道打印出行号
-v:反向输出,即显示出没有所需信息的内容
也举一个简单的例子:
ll /etc |grep 'vimrc'
显示etc目录下,含有vimrc字符串的行
简单的管道命令方面应用就是这些。
匿名管道
在实际操作中,管道作为实现进程间通信的方式,常通过pipe函数来创建,原型如下:
int pipe(int filedes[2]);
函数成功返回0,失败返回-1,数组中存放着文件描述符,即0:读入和1:写入
所谓实现进程间通信,无非就是让两个进程看到同一份代码即可,而pipe函数,就是通过在内核开辟一个公共的缓冲区,来用于两个进程间的联系。
具体的演示代码如下:
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if(-1 == ret)
{
printf("creat pipe error!error code is:%d\n",errno);
return 1;
}
pid_t id = fork();
if(id<0)
{
printf("fork error!");
return 2;
}
else if(id == 0)//child
{
close(_pipe[0]);
int i=0;
char* _mesg_c = "I am child";
for(;i<10;i++)
{
write(_pipe[1],_mesg_c,strlen(_mesg_c));
}
sleep(1);
}
else//father
{
int j=0;
close(_pipe[1]);
char _mesg[11];
while(j<10)
{
memset(_mesg,'\0',sizeof(_mesg));
read(_pipe[0],_mesg,sizeof(_mesg));
printf("%s\n",_mesg);
j++;
}
if(waitpid(id,NULL,0)<0)
{
return 3;
}
}
return 0;
}
通过fork()出一个子进程,因为管道间的单向通信,所以将子进程的读端关掉,将父进程的写端关掉,然后让子进程每隔一秒将一句“I am child”写入缓冲区,
而父进程直接读取缓冲区当中的数据。
程序最后成功的由父进程输出了十条语句。
那么我们需要考虑下面几种特殊的情况:
1 .如果所有写端都被关闭,而依然有进程读取,会怎么样呢。
给出完整的测试代码:
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if(-1 == ret)
{
printf("creat pipe error!error code is:%d\n",errno);
return 1;
}
pid_t id = fork();
if(id<0)
{
printf("fork error!");
return 2;
}
else if(id == 0)//child
{
close(_pipe[0]);
int i=0;
char* _mesg_c = "I am child";
for(;i<10;i++)
{
write(_pipe[1],_mesg_c,strlen(_mesg_c));
}
close(_pipe[1]);
sleep(1);
}
else//father
{
int j=0;
close(_pipe[1]);
char _mesg[11];
while(j<100)
{
memset(_mesg,'\0',sizeof(_mesg));
read(_pipe[0],_mesg,sizeof(_mesg));
printf("%s\n",_mesg);
j++;
}
if(waitpid(id,NULL,0)<0)
{
return 3;
}
}
return 0;
}
因为子进程写入10条就关闭了写端,而父进程会读取100,那么结果如何呢?
它会将缓存区内的剩余数据读取完,待下次read()时,便会如读到文件结尾一样退出。
2 . 如果所有的读端都被关闭,依然有进程写入数据。
那么该进程会收到信号,导致进程异常终止。
3 . 如果读端没关闭,但是并不读取数据,而写端一直在写入
当缓冲区被写满时会堵塞,知道缓冲区有新的位置为止
4 .如果写端没关闭,但是又不写入数据,而读端一直在读取数据
当剩余数据被读取完后会堵塞,知道有新的数据可供读取
后三个代码与第一个大致相同,只不过就是需要关闭或开启输入端或输出端,这里不做赘述了。
以上就是匿名管道的知识了,但是它有一个很严重的短板,就是只能在具有血缘的进程间进行通信,通常即父子间进程,这样就导致了它的局限性,那么下面就来说一下另外一种管道方式。
命名管道
正是由于上面匿名管道的缺陷,所以后来有了命名管道的出现,它没有了只能在亲缘进程间才能通信的限制。
它是通过一条特定路径创建一个管道文件,而进程双方都可以通过该路径来访问该管道文件,从而实现管道间通信的,可以看出,通过这种方式,即使毫不相干的两个进程也可以进行信息间的交流。
创建管道文件的函数原型:
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const *path,mode_t,mode);
第一个函数与第二个函数不同在于多了最后一个参数,即设备值,该值取决于文件创建的种类,只有在创建设备文件时才会使用到。
第一个参数为所要创建管道的全路径名,第二个参数为创建管道的权限,这里需要注意的是,该值会受到umask值的影响,所以最好先将umask的值设置为000。
那么接下来给出测试代码,一个进程在一端进行数据输入,另外一端实时接收数据。
server.c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);
if(mkfifo("./mypipe", 0666 |S_IFIFO) < 0 )//初始权限为666
{
perror("mkfifo");
return 1;
}
int fd = open("./mypipe",O_RDONLY);//以只读方式打开
if(fd<0)//管道打开失败
{
perror("open");
return 2;
}
char buf[1024];//定义一块缓冲区
while(1)
{
ssize_t s = read(fd,buf,sizeof(buf)-1);//将数据从管道文件读取到buf中
if(s>0)
{
buf[s-1]=0;
printf("client say# %s\n",buf);
}
else
{
printf("client quit,server quit!");
break;
}
}
close(fd);
return 0;
}
client.c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
int fd = open("./mypipe",O_WRONLY);
if(fd<0)
{
perror("open");
return 1;
}
char buf[1024];
while(1)
{
printf("Please Enter:");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);//文件描述符0为标准输入端,即将键盘上输入的数据读取到buff中
{
buf[s]=0;
write(fd,buf,strlen(buf));//然后将buff中的数据又写入到管道文件中
}
}
close(fd);
}
顺便给出上面代码的Makefile
.PHONY:all
all:client server
client:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server mypipe
那么通过上面的代码,就可以实现client程序将键盘上输入的数据读进buff里,然后从buff写入到管道文件,server程序将数据从管道文件读取到buf中,然后输出buf中的数据。
最终的测试如下,在一端输入,另一端接收。
那么系统分配的管道大小是多少呢?我们可以一直给管道中写东西,一直到写满为止来测试它的最大容量,也可以通过查询系统中的文件。
命令行指令:
cat /proc/sys/kernel/msgmax
然后我们在通过小代码测试一下:
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if(-1 == ret)
{
printf("creat pipe error!error code is:%d\n",errno);
return 1;
}
pid_t id = fork();
if(id<0)
{
printf("fork error!");
return 2;
}
else if(id == 0)//child
{
close(_pipe[0]);
long long count = 0;
char* _mesg_c = "1";
while(1)
{
write(_pipe[1],_mesg_c,1);
printf("count = %d\n",++count);
}
}
else//father
{
char _mesg[10];
while(1)
{
memset(_mesg,'\0',sizeof(_mesg));
}
}
return 0;
}
可以看到测试的管道大小和系统给定的一样,说明没有问题喔。