管道
Posted 别呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了管道相关的知识,希望对你有一定的参考价值。
文章目录
一、管道概述
1.1、管道概念
管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
我们通常把是把一个进程的输出连接或“管接”(经过管道来连接)到另一个进程的输入。
1.2、管道特点
①管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。
②只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
③单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中。
④数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且都是从缓冲区的头部读出数据。
1.3、管道创建(通过pipe函数)
#include <unistd.h>
int pipe(int fd[2]);
功能:创建一无名管道。
参数:
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:
成功返回0,失败返回错误代码
1.4、管道应用实例
(通过pipe在父子进程之间传递数据)
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main()
{
int fda[2];
char buf[30];
int curflag;
if (pipe(fda)==-1)
{
printf("error\\n");
}
switch (fork())
{
case -1:
printf("error\\n");
break;
case 0:
read(fda[0],buf,30);
printf("child read:%s\\n",buf);
close(fda[1]);
close(fda[0]);
break;
default:
write(fda[1],"your data is coming!",30);
close(fda[0]);
close(fda[1]);
break;
}
}
------------------
运行结果:
child read:your data is coming!
1.5、管道读写规则
①如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。
②当没有数据可读时,read调用就会阻塞,即进程暂停执行,一直等到有数据来到为止。
③如果管道的另一端已经被关闭,也就是没有进程打开这个管道并向它写数据时,read调用就会阻塞。
④如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0。
⑤当管道的写端存在时,如果请求的字节数目大于PIPE_BUF
,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF
,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。
⑥向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。
1.6、管道的局限性
管道的主要局限性正体现在它的特点上:
① 只支持单向数据流;
② 只能用于具有亲缘关系的进程之间;
③ 没有名字;
④ 管道的缓冲区是有限的(管道存在于内存中,在管道创建时,为缓冲区分配一个页面大小);
⑤ 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须实现约定好数据的格式,比如多少字节算一个消息(或命令、或记录)等待;
二、fifo有名管道
2.1、有名管道概念
管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(named pipe 或 FIFO)提出后,该限制得到了克服。FIFO 不同于管道之处在于它提供一个路径名与之关联,以 FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能够交换数据。值得注意的是,FIFO严格遵循先进先出,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如 lseek() 等文件定位操作。
2.2、有名管道创建
命名管道可以从命令行上创建,推荐的命令行方法是使用下面这个命令:$ mkfifo filename
命名管道也可以从程序里创建,相关函数有:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
int mknod(const char *filename,mode_t mode|S_IFIFO,(dev_t) 0);
参数:
pathname: 创建后FIFP名字
mode: 和打开普通文件Open函数的mode参数相同
返回值:
如果第一参数已存在,返回EEXIST;
有名管道创建实例
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int res = mkfifo("my_fifo",0777);
if(res==0) printf("FIFO created\\n");
exit(EXIT_SUCCESS);
}
2.3、名管道的打开规则
有名管道比管道多了一个打开操作:open
FIFO的打开规则:
① 当只写打开FIFO管道时,如果没有FIFO没有读端打开,则open写打开会阻塞。
② FIFO内核实现时可以支持双向通信。(pipe单向通信,因为父子进程共享同一个file结构体)
③ FIFO可以一个读端,多个写端;也可以一个写端,多个读端。
注意问题:
①程序不能以O_RDWR模式打开FIFO文件进行读写。
②如果确实需要在程序之间双向传递数据的话,我们可以同时使用一对FIFO或管道,一个方向配一个;还可以用先关闭再重新打开FIFO的办法明确地改变数据流的方向。
有名管道打开实例:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "my_fifo"
int main(int arg, char *argv[])
{
int res;
int op_m = 0;
if(arg < 2) {
fprintf(stderr,"Usage: %s<some \\
combination of O_RDONLY O_WRONLY or \\
O_NONBLOCK>\\n",*argv);
exit(EXIT_FAILURE);
}
argv++;
if(strncmp(*argv,"O_RDONLY",8)==0)op_m|=O_RDONLY;
if(strncmp(*argv,"O_WRONLY",8)==0)op_m|=O_WRONLY;
if(strncmp(*argv,"O_NONBLOCK",10)==0)
op_m |= O_NONBLOCK;
argv++;
if(*argv) {
if(strncmp(*argv,"O_RDONLY",8)==0)op_m|=O_RDONLY;
if(strncmp(*argv,"O_WRONLY",8)==0)op_m|=O_WRONLY;
if(strncmp(*argv,"O_NONBLOCK",10)==0)
op_m |= O_NONBLOCK;
}
if(access(FIFO_NAME, F_OK) == -1) {
res = mkfifo(FIFO_NAME,0777);
if(res != 0) {
fprintf(stderr,"Could not create fifo\\
%s\\n",FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO\\n",getpid());
res = open(FIFO_NAME,op_m);
printf("Process %d result %d\\n",getpid(),res);
sleep(5);
if(res != -1) close(res);
printf("Process %d finished\\n",getpid());
exit(0);
}
2.4、有名管道读写规则
1、如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。
2、对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。
3、如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。
约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。如果一个进程为了从FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的读操作。
对于没有设置阻塞标志的写操作:
①当要写入的数据量大于PIPE_BUF
时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
②当要写入的数据量不大于PIPE_BUF
时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN
错误,提醒以后再写。
写FIFO程序实例
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#define FIFO_NAME "my_fifo"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024*1024*10)
int main()
{
int pipe_fd;
int res;
int op_mode = O_WRONLY;
int bytes_sent = 0;
char buffer[BUFFER_SIZE + 1]="hello boys and girls!";
if(access(FIFO_NAME,F_OK) == -1) {
res = mkfifo(FIFO_NAME,0777);
if(res != 0) exit(EXIT_FAILURE);
}
pipe_fd = open(FIFO_NAME,O_CREAT|O_WRONLY);
if(pipe_fd != -1){
while(bytes_sent < TEN_MEG){
res = write(pipe_fd,buffer,BUFFER_SIZE);
if(res == -1 ) exit(EXIT_FAILURE);
bytes_sent += res;
}
close(pipe_fd);
}
else {
exit(EXIT_FAILURE);
}
exit(0);
}
读FIFO程序实例
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#define FIFO_NAME "my_fifo"
#define BUFFER_SIZE PIPE_BUF
int main()
{
int pipe_fd;
int res;
int op_mode = O_RDONLY;
char buffer[BUFFER_SIZE + 1] = {0};
int bytes_read = 0;
pipe_fd = open(FIFO_NAME,op_mode);
if(pipe_fd != -1) {
do{
res = read(pipe_fd,buffer,BUFFER_SIZE);
bytes_read += res;
} while(res > 0);
close(pipe_fd);
}
else {
exit(EXIT_FAILURE);
}
printf("Process %d finished,%d bytesread\\n",getpid(),bytes_read);
printf("receive data:%s",buffer);
exit(0);
}
测试结果:
Process 4829 finished,10485760 bytesread
receive data:hello boys and girls!
以上是关于管道的主要内容,如果未能解决你的问题,请参考以下文章