Linux系统编程与网络编程——管道FIFO(十二)

管道基本概念

管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,管道建立后,实际上是获得两个文件描述符:一个用与读取而另一个用于写入。任何从管道写入端写入的数据,可以从管道读取端读出。

管道通信具有以下特点:
(1)管道是半双工的,数据只能向一个方向流动,需要双方通信时,要建立起两个管道。
(2)管道存放在内存中,是一种独立的文件系统。


无名管道的创建与读写

系统调用pipe()用于创建一个管道,其函数原型如下:
在这里插入图片描述
pipe()将建立一对文件描述符,放到参数 pipefd 中。 pipefd[0] 文件描述符用来从管道中读取数据, pipefd[1]用于写入数据到管道。

单个进程中的管道几乎没有任何意义,通常,进程会先调用pipe,接着调用fork,从而创建从父进程到子进程的通道。

fork出子进程后,父进程的文件描述表也复制到子进程,于是如下图:
在这里插入图片描述
关闭掉父进程的fd[0], 以及子进程的fd[1],则父进程可以发送消息给子进程,反之亦然。
在这里插入图片描述
源代码:pipe_test.c

#include <stdio.h>
#include <unistd.h>

void child_process(int pipefd[])
{
	int i = 0;
	char writebuf[128] = {0};
	/*子进程关闭读文件描述符*/
	close(pipefd[0]);
	while(1)
	{
		sprintf(writebuf, "write pipe : %d", i);
		/*子进程往管道写入数据*/
		write(pipefd[1], writebuf, strlen(writebuf));
		printf("write pipe: %s\n", writebuf);
		i = (i + 1) % 10;
		sleep(1);
	}
} 
void father_process(int pipefd[])
{
	char readbuf[128] = {0};
	/*父进程关闭写文件描述符*/
	close(pipefd[1]);
	while(1)
	{
		/*父进程从管道中读取数据*/
		read(pipefd[0], readbuf, sizeof(readbuf));
		printf("read pipe: %s\n", readbuf);
	}
} 

int main(int argc, char** argv)
{
	int pipefd[2];
	pid_t pid;
	if( pipe(pipefd) == -1)
	{
		perror("pipe:");
		return -1;
	} 
	pid = fork();
	if(pid == 0)
	{
		child_process(pipefd);
	} 
	else if(pid != -1)
	{
		father_process(pipefd);
	} 
	else
	{
		perror("fork:");
	}
	return 0;
}

运行结果:子进程写数据,父进程读出数据并且打印。
在这里插入图片描述
注意:匿名管道只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。


有名管道FIFO

FIFO是first in first out(先进先出)的缩写,FIFO也称为“命名管道”。FIFO是一种特殊类型的管道,它在文件系统中有一个相应的文件,称为管道文件。

FIFO文件可以通过 mkfifo() 函数创建。在FIFO文件创建之后,任何一个具有适当权限的进程都可以打开FIFO文件。

扫描二维码关注公众号,回复: 5874385 查看本文章

mkfifo()函数原型为:
在这里插入图片描述
参数:
pathname 一个FIFO文件的路径名。
mode 与普通文件 creat() 函数中的mode参数相同。

返回值:
如果要创建的文件已经存在,返回-1, errno 为 EEXIST 错误, 成功返回0。
一般文件的I/O函数都可以用于FIFO,如open()、read()、write()、close()等等。

源代码fifo_write_test.c

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

void handle_sig(int sig)
{
	printf("signal pipe\n");
	exit(-1);
} 

int main(int argc, char** argv)
{
	int fd;
	int ret;
	char buf[128];
	int i = 0;
	/*处理管道信号*/
	signal(SIGPIPE, handle_sig);
	if(mkfifo("./fifo", 0640) == -1)
	{
		if(errno != EEXIST)
		{
			perror("mkfifo");
			return -1;
		}
	} 
	/*只写方式打开管道*/
	fd = open("./fifo", O_WRONLY);
	if(fd == -1)
	{
		perror("open");
		return -1;
	}
	while(1)
	{
		sprintf(buf, "data %d", i++);
		/*往管道写数据*/
		ret = write(fd, buf, strlen(buf));
		printf("write fifo [%d] %s\n", ret, buf);
		sleep(1);
	} 
	return 0;
}

源码中,通过mkfifo来创建FIFO文件,并且以只写 的方式打开,只有当两边的管道都打开的时候才能写进去,否则阻塞在 write() 函数上。

源代码fifo_read_test.c

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc, char** argv)
{
	int fd;
	int ret;
	char buf[128];
	if(mkfifo("./fifo", 0640) == -1)
	{
		if(errno != EEXIST) /*如果错误类型是fifo文件已经存在,则继续执行*/
		{
			perror("mkfifo");
			return -1;
		}
	} 
	/*以只读方式打开管道*/
	fd = open("./fifo", O_RDONLY);
	if(fd == -1)
	{
		perror("open");
		return -1;
	}
	while(1)
	{
		memset(buf, 0, sizeof(buf));
		/*读管道*/
		ret = read(fd, buf, sizeof(buf) - 1);
		printf("read fifo [%d] : %s\n", ret, buf);
		sleep(1);
	} 
	return 0;
}

源代码中,通过 mkfifo() 函数创建FIFO管道,如果已经存在那么就直接只读方式打开,如果另一端没有被打开,则阻塞在 read() 函数上,如果另一端打开后关闭,则 read() 一直读到EOF也就是0个字节。


管道容量

管道有它的容量大小,通过man 7 pipe来查看。默认情况下为64k字节,也可以通过fcntl函数来查看:
在这里插入图片描述
例如:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#define _GNU_SOURCE

int main()
{
	if(mkfifo("./fifo", 0640) == -1)
	{
		if(errno != EEXIST)
		{
			perror("mkfifo");
			return -1;
		}
	}
	/*只写方式打开管道*/
	int fd = open("./fifo", O_WRONLY | O_NONBLOCK);
	if(fd == -1)
	{
		perror("open");
		return -1;
	}
	printf("pipe size %d\n", fcntl(fd, F_GETPIPE_SZ));
	fcntl(fd, F_SETPIPE_SZ, 4096 * 3); /*必须填写4096的整数倍*/
	printf("pipe size %d\n", fcntl(fd, F_GETPIPE_SZ));
	return 0;
}

注意事项

使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):
1.如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。

2.如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

3.如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。讲信号时会讲到怎样使SIGPIPE信号不终止进程。

4.如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。


查看系统限制
在这里插入图片描述
pathconf函数返回配置文件的限制值,是与文件或目录相关联的运行时限制。path参数是你想得到限制值的路径,name是想得到限制值的名称,name的取值主要有以下几个取值:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Gsunshine24/article/details/89031695