管道是进程间通信的主要手段之一。一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端。管道是一种特殊的文件,它不属于某一种文件系统,而是一种独立的文件系统,有其自己的数据结构。根据管道的适用范围将其分为:无名管道和命名管道。
●无名管道
主要用于父进程与子进程之间,或者两个兄弟进程之间。在linux系统中可以通过系统调用建立起一个单向的通信管道,且这种关系只能由父进程来建立。因此,每个管道都是单向的,当需要双向通信时就需要建立起两个管道。管道两端的进程均将该管道看做一个文件,一个进程负责往管道中写内容,而另一个从管道中读取。这种传输遵循“先入先出”(FIFO)的规则。
●命名管道
命名管道是为了解决无名管道只能用于近亲进程之间通信的缺陷而设计的。命名管道是建立在实际的磁盘介质或文件系统(而不是只存在于内存中)上有自己名字的文件,任何进程可以在任何时间通过文件名或路径名与该文件建立联系。为了实现命名管道,引入了一种新的文件类型——FIFO文件(遵循先进先出的原则)。实现一个命名管道实际上就是实现一个FIFO文件。命名管道一旦建立,之后它的读、写以及关闭操作都与普通管道完全相同。虽然FIFO文件的inode节点在磁盘上,但是仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,和普通管道相同。
环形缓冲区
每个管道只有一个页面作为缓冲区,该页面是按照环形缓冲区的方式来使用的。这种访问方式是典型的“生产者——消费者”模型。当“生产者”进程有大量的数据需要写时,而且每当写满一个页面就需要进行睡眠等待,等待“消费者”从管道中读走一些数据,为其腾出一些空间。相应的,如果管道中没有可读数据,“消费者”进程就要睡眠等待,
有名管道的介绍
无名管道,由于没有名字,只能用于亲缘关系的进程间通信.。为了克服这个缺点,提出了有名管道(FIFO)。
FIFO不同于无名管道之处在于它提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO不相关的进程也能交换数据。值的注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
注意:有名管道的名字存在于文件系统中,内容存放在内存中。
有名管道的创建
该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode参数相同。如果mkfifo的一个参数是一个已经存在路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。
有名管道的打开规则
有名管道比无名管道多了一个打开操作:open
FIFO的打开规则:
如果当前打开操作时为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。
如果当前打开操作时为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENIO错误(当期打开操作没有设置阻塞标志)。
/************
打开fifo
写函数
***************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
int fd;
if(argc < 2)
{
fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
exit(EXIT_FAILURE);
}
if(mkfifo(argv[1],0666) < 0 && errno != EEXIST)
{
fprintf(stderr,"Fail to mkfifo %s : %s.\n",argv[1],strerror(errno));
exit(EXIT_FAILURE);
}
if((fd = open(argv[1],O_WRONLY)) < 0)
{
fprintf(stderr,"Fail to open %s : %s.\n",argv[1],strerror(errno));
exit(EXIT_FAILURE);
}
printf("open for write success.\n");
return 0;
}
//打开读fifo文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
int fd;
if(argc < 2)
{
fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
exit(EXIT_FAILURE);
}
if(mkfifo(argv[1],0666) < 0 && errno != EEXIST)
{
fprintf(stderr,"Fail to mkfifo %s : %s.\n",argv[1],strerror(errno));
exit(EXIT_FAILURE);
}
if((fd = open(argv[1],O_RDONLY)) < 0)
{
fprintf(stderr,"Fail to open %s : %s.\n",argv[1],strerror(errno));
exit(EXIT_FAILURE);
}
printf("open for read success.\n");
return 0;
}
如果open时没有使用O_NONBLOCK参数,我们发现不论读端还是写端先打开,先打开者都会阻塞,一直阻塞到另一端打开。
有名管道的读写规则
A.从FIFO中读取数据
约定:如果一个进程为了从FIFO中读取数据而以阻塞的方式打开FIFO, 则称内核为该进程的读操作设置了阻塞标志
<1>如果有进程为写而打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说返回-1,当前errno值为EAGAIN,提醒以后再试。
<2>对于设置阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其他进程正在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论写入数据量的大小,也不论读操作请求多少数据量。
<3>如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞
<4>如果写端关闭,管道中有数据读取管道中的数据,如果管道中没有数据读端将不会继续阻塞,此时返回0。
注意:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。
B.向FIFO中写入数据
约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作设置了阻塞标志。
对于设置了阻塞标志的写操作:
<1>当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳写入的字节数时,才开始进行一次性写操作。
<2>当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
对于没有设置阻塞标志的写操作:
<1>当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
<2>当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写。
注意:只有读端存在,写端才有意义。如果读端不在,写端向FIFO写数据,内核将向对应的进程发送SIGPIPE信号(默认终止进程);
//向fifo写入数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAX 655360
int main(int argc,char *argv[])
{
int n,fd;
char buf[MAX];
if(argc < 2)
{
fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
exit(EXIT_FAILURE);
}
if(mkfifo(argv[1],0666) < 0 && errno != EEXIST) //创建fifo文件
{
fprintf(stderr,"Fail to mkfifo %s : %s.\n",argv[1],strerror(errno));
exit(EXIT_FAILURE);
}
if((fd = open(argv[1],O_WRONLY )) < 0) //打开fifo写
{
fprintf(stderr,"Fail to open %s : %s.\n",argv[1],strerror(errno));
exit(EXIT_FAILURE);
}
printf("open for write success.\n");
while(1)
{
printf(">");
scanf("%d",&n); //输入数据
n = write(fd,buf,n); //写入数据
printf("write %d bytes.\n",n); //打印写入多少字节
}
exit(EXIT_SUCCESS);
}
//从fifo读数据
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAX 655360
int main(int argc,char *argv[])
{
int fd,n;
char buf[MAX];
if(argc < 2)
{
fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
exit(EXIT_FAILURE);
}
if(mkfifo(argv[1],0666) < 0 && errno != EEXIST) //创建fifo文件
{
fprintf(stderr,"Fail to mkfifo %s : %s.\n",argv[1],strerror(errno));
exit(EXIT_FAILURE);
}
if((fd = open(argv[1],O_RDONLY )) < 0) //打开fifo为只读
{
fprintf(stderr,"Fail to open %s : %s.\n",argv[1],strerror(errno));
exit(EXIT_FAILURE);
}
printf("open for read success.\n");
while(1)
{
printf(">");
scanf("%d",&n);
n = read(fd,buf,n);
printf("Read %d bytes.\n",n);
}
exit(EXIT_SUCCESS);
}
实例分析
1.创建txt文档,向文档里写入数据
/*******************
创建txt文档,名为data.txt
向文档里写入1000遍buffer保存入文档
*********************/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void filecopy(FILE *,char *); //函数声明
int main(void)
{
FILE *fp1;
long int i = 1000;
char buf[] = "Welcome to coolwriter's blog!\n";
char *file1 = "data.txt";
printf("begin!\n");
if((fp1 = fopen(file1,"a+")) == NULL ) //"a+" 读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
{
printf("can't open %s\n",file1);
}
while(i--)
filecopy(fp1,buf); // 循环调用1000遍filecopy函数
fclose(fp1); //关闭文件
printf("over!\n"); //打印“结束”
return 0;
}
void filecopy(FILE *ifp,char *buf) //写数据函数,其中*ifp文件指针,*buf数据
{
char c;
int i,j;
j = 0;
i = strlen(buf)-1; //除去最后字符'/n'
while(i--){ //循环buf的长度遍
putc(buf[j],ifp); //向文件ifp写入字符,每遍写入一个字符
j++; //移动buf数组中的字符位置号
}
putc('\n',ifp); //向文件ifp写入字符'/n'
}
/*******************
创建fifo
把数据文件 data.txt中的数据
写入到fifo管道
*********************/
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
int main()
{
const char *fifo_name = "my_fifo"; //定义fifo文件
char *file1 = "data.txt"; //定义数据文件
int pipe_fd = -1;
int data_fd = -1;
int res = 0;
const int open_mode = O_WRONLY;
int bytes_sent = 0;
char buffer[PIPE_BUF + 1];
if(access(fifo_name, F_OK) == -1) //判断文件是否存在
{
//管道文件不存在
//创建命名管道
res = mkfifo(fifo_name, 0777); //创建fifo管道
if(res != 0)
{
fprintf(stderr, "Could not create fifo %s\n", fifo_name);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY\n", getpid()); //打印进程号
pipe_fd = open(fifo_name, open_mode); //以只写入阻塞方式打开FIFO文件
data_fd = open(file1, O_RDONLY); //以只读方式打开数据文件
printf("Process %d result %d\n", getpid(), pipe_fd); //打印进程号
if(pipe_fd != -1)
{
int bytes_read = 0;
//向数据文件读取数据
bytes_read = read(data_fd, buffer, PIPE_BUF); //PIPE_BUF在/usr/include/linux/limits.h中定义 4096bytes
buffer[bytes_read] = '\0'; //最后数组的结束符
while(bytes_read > 0) //读写成功
{
//向FIFO文件写数据
res = write(pipe_fd, buffer, bytes_read); //读写成功
if(res == -1)
{
fprintf(stderr, "Write error on pipe\n");
exit(EXIT_FAILURE);
}
//累加写的字节数,并继续读取数据
bytes_sent += res;
bytes_read = read(data_fd, buffer, PIPE_BUF);
buffer[bytes_read] = '\0';
}
close(pipe_fd);
close(data_fd);
}
else
exit(EXIT_FAILURE);
printf("Process %d finished\n", getpid());
exit(EXIT_SUCCESS);
}
/*******************
创建文档DataFormFIFO.txt
把fifo管道中的数据读出来
保存到DataFormFIFO.txt文档里面
*********************/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <string.h>
int main()
{
const char *fifo_name = "my_fifo";
int pipe_fd = -1;
int data_fd = -1;
int res = 0;
int open_mode = O_RDONLY;
char buffer[PIPE_BUF + 1];
int bytes_read = 0;
int bytes_write = 0;
memset(buffer, '\0', sizeof(buffer)); //清空缓冲数组
printf("Process %d opening FIFO O_RDONLY\n", getpid());
pipe_fd = open(fifo_name, open_mode); //以只读阻塞方式打开管道文件,注意与fifowrite.c文件中的FIFO同名
data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644); //以只写方式创建打开保存数据的文件
printf("Process %d result %d\n",getpid(), pipe_fd);
if(pipe_fd != -1)
{
do
{
//读取FIFO中的数据,并把它保存在文件DataFormFIFO.txt文件中
res = read(pipe_fd, buffer, PIPE_BUF);
bytes_write = write(data_fd, buffer, res);
bytes_read += res;
}while(res > 0);
close(pipe_fd);
close(data_fd);
}
else
exit(EXIT_FAILURE);
printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
exit(EXIT_SUCCESS);
}