管道

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!

以上是关于管道的主要内容,如果未能解决你的问题,请参考以下文章

r 计算管道的步骤(基本片段)

15种Python片段去优化你的数据科学管道

如何在降价表的代码语句中转义管道字符?

渲染管道光栅阶段一“总览”

渲染管道光栅阶段一“总览”

服务器在管道中的“龙头”地位