进程通讯之无名管道

进程通讯之无名管道


一、引言:
前面我们讲过,管道是进程通讯的一种,并且管道分为有名管道和无名管道,那么二者之间有什么区别呢,对于前面我们所说的有名管道的使用已经得到了实现,那么无名管道是怎么使用的,具体的过程呢。

二、无名管道
我们说过有名管道在文件系统中存在一个标识,也就是文件名,所以才叫有名管道,而无名管道恰恰相反,可以理解成为没有名称,也就是在文件系统中没有标识的一个管道文件,其实它是不存在的,因为在函数中使用的时候才去创建它,当程序结束的时候,就会销毁,不复存在。

三、创建管道pipe函数

说到创建管道,我们这里引入一个函数,pipe函数:

pipe函数用于创建一个管道,以实现进程间的通讯,pipe函数的定义如下:
#include
int pipe(int fd[2]);

pipe函数的参数是包含两个int型整数的数组指针。该函数成功时返回0;并将一对打开的文件描述符值填入其参数指向的数组。如果失败,则返回-1并设置errno。
通过pipe函数创建的这两个文件描述符fd[0]和fd[1]分别构成了管道的两端,往fd[1]写入的数据可以从fd[0]读出。并且,fd[0]只能从管道读出数据,fd[1]只能用于往管道写入数据,而不能反过来使用。
如果要实现双向的数据传输,就应该使用两个管道,因为管道都是单向的半双工通讯。默认情况下,这一对文件描述符都是阻塞的,此时如果我们用read系统调用来读取一个空的管道,则read将被阻塞,直到管道内有数据可读;如果用write系统调用来往一个满的管道写入数据,write也将会被阻塞,直到管道中有多余空间可使用。

如果管道的写端文件描述符fd[1]的引用计数减少至0,即没有任何的进程需要往管道中写入数据,则针对该管道的读端文件描述符fd[0]的read操作将返回0;,即读取到了文件结束标识(End Of File,EOF);反之,如果管道的读端文件描述符fd[0]的引用计数减少至0,即没有任何的进程需要从管道读取数据,则针对该管道的写端文件描述符的fd[1]的write操作失败,并引发SIGPIPE 信号。

四、无名管道的使用解释:

管道内部传输的数据是字节流。

关于无名管道的使用要求是:只能用于两个有关联的进程之间的通信(如父、子进程的通信),而对于父子进程之间的通信,创建管道文件和打开管道文件必须是在fork之前完成。如果是在fork之后完成,那么子进程与父进程将分别创建一个管道,那么父子进程将无法互相关联的进行通信,从而不能实现进程的通讯。

如图所示:图解:文件描述符共享


而对于无名管道通讯的依赖依据是:

fork调用之后,父子进程文件描述符共享。而我们说过在无名管道中调用函数pipe来创建管道文件,这里父子进程通讯传递数据的原因就是fork调用之后两个管道文件描述符(fd[0] 和fd[1])都保持打开。一对这样的文件描述符只能保证父子进程间一个方向的数据传输,父子进程之间必须关闭一对读写,即必须有一个关闭fd[0],另外一个关闭fd[1],,比如我们要使用管道文件实现从父进程向子进程写数据,如图所示:


从上图也可以看出,如果实现父子进程之间的双向数据传输,就必须使用两个管道。

无名管道的操作:
读:int read(int fd, void *buff, size_t size);
写: int write(int fd, void *buff, size_t size);
关闭:int close(int fd);

五、练习:
题目要求:
父进程循环接收用户输入,子进程统计用户输入的单词个数

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include <ctype.h>

void main()
{
	int fds[2];
	pipe(fds);
	
	pid_t pid = fork();
	if (pid == 0)
	{
		close(fds[1]);
		int count = 0;
		while(1)
		{
			char buff[128] = {0};
			read(fds[0],buff,127);
			int i = 0;
			for (; i< strlen(buff);i++)
			{
				if (isalpha(buff[i]) && !isalpha(buff[i+1]))
				{
					i++;
					count++;
				}
			}
			printf("单词个数 : %d\n",count);
		}
		close(fds[0]);
	}
	else
	{
		close(fds[0]);
		while(1)
		{
			sleep(1);
			printf("plese input: \n");
			char buff[128] = {0};
			fgets(buff,127,stdin);
			write(fds[1],buff,strlen(buff));
		}
		close(fds[1]);
	}

	exit(0);
}

运行结果如下:


分析:父进程执行写操作之前,先要关闭与之对应的读操作fds[0],而子进程执行读操作的之前,要先关掉写操作fds[1],因为一对描述符只能保证一个方向的数据运输,这样的话必须关掉一对读写操作,保证信息的正确传递。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值