学习Linux的知识中,有一个特别重要的概念叫“进程”,而要进行进程间通信时,有一个特别重要的概念就是--管道,今天,我们就来学习一下什么是管道,它能又干什么呢?
一、概念
管道:把一个进程连接到另外一个进程的一个数据流称为管道。
(其实,我们联系现实生活,自来水管可以将我们用户和供水站连接起来,通过管道运输水流,在这里可以借助这个例子 帮助我们理解进程间通信中管道的概念)
二、匿名管道
1.原型:
int pipe(int fd[2]);
//fd为文件描述符,其中fd[0]表示读端,fd[1]表示写端
2.下面,我们来实现一段从键盘读取数据,写入管道,读取管道,写到屏幕的代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(void)
{
int fds[2];
char buf[100];
int len;
if (pipe(fds) == -1)
perror("make pipe"), exit(1);
//读取数据
while (fgets(buf, 100, stdin)){
len = strlen(buf);
//写入管道
if (write(fds[1], buf, len) != len){
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
//读取管道
if ((len = read(fds[0], buf, 100)) == -1){
perror("read from pipe");
break;
}
//写到屏幕
if (write(1, buf, len) != len){
perror("write to stdout");
break;
}
}
}
3.通过父进程fork出子进程通过管道连接的过程如下:
(1)父进程创建管道
(2)父进程fork出子进程(左边父进程,右边子进程)
(3)父进程关闭fd[0],子进程关闭fd[1]
管道的读写操作是相对应的,当父进程fork出子进程时,两边对同时打开读写端,这样会导致程序不知道到底在管道哪一端读数据,哪一端写数据。因此,我们必须关闭关闭父进程的一端和子进程对应的另外一端,这样才能进行正常的读写操作。
说明:管道具有原子性,当写入管道的数据不大于pipe_buf时,linux将保证写入数据的原子性;当写入管道的数据大于pipe_buf时,linux将不保正写入数据的原子性。
4.匿名管道的特点
(1)只能用于具有共同祖先的进程(有亲缘关系)之间进行通信。通常,一个管道由一个进程创建,然年该进程调用fork,之后 父子进程就可以用该管道进行通信;
(2)管道提供流式服务;
(3)管道的生命周期随进程(一般而言,进程退出,管道释放);
(4)一般而言,内核会对管道进行同步和互斥;
(5)管道是半双工的,数据只能由一个方向流动;双方通信时,需要建立两个管道。
这里可能会有一些比较陌生的词语,我们来解释一下:
(1)把两个进程看到的公共资源叫做临界资源,这两个进程的代码叫做临界区;
(2)管道在访问临界资源时,既要保证互斥,又要保证访问的原子性;
(3)同步是指在保证数据安全时,按照某种特性进行。
三、命名管道
看了匿名管道之后,我们肯定会有疑问,具有亲缘关系的管道之间可以进行通信,那么几个不相关的进程之间是否可以进行通呢?这里我们就引进了命名管道的概念。
1.命名管道:通过FIFO文件来进行不相关进程的通信,命名管道是一种特殊类型的文件
2.命名管道的创建
(1)在命令行创建
$ mkfifo filename
(2)从程序里创建
int mkfifo(const char * filename, mode_t mode);
(3)创建
int main(int argc, char * argv[])
{
mkfifo("p2", 0644);
return 0;
}
四、匿名管道和命名管道的区别
1.匿名管道由pipe函数创建并打开;而命名管道由mkfifo,用open打开。
2.匿名管道(pipe)和命名管道(FIFO)最大的差别就是管道的创建和打开不同,一旦工作完成后,它们具有相同的语义。
下面,我们用命名管道来实现文件拷贝
读取文件,写入管道:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while (0)
int main(int argc, char * argv[])
{
mkfifo("tp", 0644);
int infd;
infd = open("abc", O_RDONLY);
if (infd == -1)
ERR_EXIT("open");
int outfd;
outfd = open("tp", O_WRONLY);
if (outfd == -1)
ERR_EXIT("open");
char buf[1024];
int n;
while ((n == read(infd, buf, 1024)) > 0)
{
write(outfd, buf, n);
}
close(infd);
close(outfd);
return 0;
}
读取管道,写入目标文件:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#define ERR_EXIT(m)\
do\
{\
perror(m); \
exit(EXIT_FAILURE); \
}while (0)
int main(int argc, char * argv[])
{
int outfd;
outfd = open("abc.bak",O_WRONLY|O_TRUNC,0644);
if (outfd == -1)
ERR_EXIT("open");
int infd;
outfd = open("tp", O_RDONLY);
if (outfd == -1)
ERR_EXIT("open");
char buf[1024];
int n;
while ((n = read(infd, buf, 1024)) > 0)
{
write(outfd, buf, n);
}
close(infd);
close(outfd);
unlink("tp");
return 0;
}