进程间通信----管道

PIPE:无名管道

<span style="font-family:Microsoft YaHei;font-size:18px;">#include  <unistd.h>
int  pipe(int fd[2]);    //返回值:若成功,返回0,若出错,返回-1</span>

经由参数fd返回两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。fd[1]的输出是fd[0]的输入。


<span style="font-family:Microsoft YaHei;font-size:18px;"></span><pre name="code" class="cpp"><span style="font-family:Microsoft YaHei;font-size:18px;">#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	pid_t pid;
	int temp;
	int fd[2];
	char s[14] = "test message";
	char d[14];

	if(pipe(fd) == -1)
	{
		perror("pipe");
		exit(EXIT_FAILURE);
	}
	if((pid = fork()) < -1)
	{
		perror("fork");
		exit(EXIT_FAILURE);
	}
	else if(pid == 0)	//子进程
	{
		close(fd[0]);	//关闭读端
		printf("now, write data to pipe\n");
		if((write(fd[1], s, 14)) == -1)
		{
			perror("write");
			exit(EXIT_FAILURE);
		}
		else
		{
			printf("the written data is %s\n", s);
			exit(EXIT_FAILURE);
		}
	}
	else if(pid > 0)	//父进程
	{
		close(fd[1]);	//关闭写端
		sleep(2);
		printf("now, read data from pipe\n");
		if ((read(fd[0], d, 14)) == -1)
		{
			perror("read");
			exit(EXIT_FAILURE);
		}
		printf("the data from pipe is %s\n", d);
	}
	return 0;
}
</span>

 


POPEN和PCLOSE

<span style="font-family:Microsoft YaHei;font-size:18px;">#include  <stdio.h>
FILE  *open(const char *cmdstring, const char *type);//返回值:若成功,返回文件指针;若出错,返回NULL
int  pclose(FILE *fd);//返回值:若成功,返回cmdstring的终止状态;若出错,返回-1</span>
函数popen先执行fork,然后调用exec执行cmdstring,并且返回一个标准I/O文件指针。

如果type是“r”,则文件指针连接到cmdstring的标准输出。

如果type是“w”,则文件指针连接到cmdstring的标准输入。


<span style="font-family:Microsoft YaHei;font-size:18px;"></span><pre name="code" class="cpp"><span style="font-family:Microsoft YaHei;font-size:18px;">#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>

int main(int argc, char *argv[])
{
	FILE *finput, *foutput;
	char buffer[PIPE_BUF];
	int n;
	finput = popen("echo test!", "r");				//将echo test命令的输出与读端相连
	foutput = popen("cat", "w");					//将cat命令的输入与写端相连	
	read(fileno(finput), buffer, strlen("test!"));  //读echo test的输出结果到buf
	write(fileno(foutput), buffer, strlen("test!"));//将管道内容读出作为cat输入
	pclose(finput);									//关闭流
	pclose(foutput);
	printf("\n");
	exit(EXIT_SUCCESS);
}
</span>

 


FIFO:有名管道

未命名的管道只能在两个相关的进程之间使用,而且这两个相关的进程还要有一个共同的创建了它们的祖先进程。但是通过FIFO,不相关的进程也能交换数据。

<span style="font-family:Microsoft YaHei;font-size:18px;">#include  <sys/stat.h>
int  mkfifo(const char *path, mode_t mode);
int  mkfifoat(int fd, const char *path, mode_t mode);
//两个函数的返回值: 若成功,返回0;若出错,返回-1.</span>


mkfifo的第一个参数是一个普通的路劲名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode参数相同。如果mkfifo的一个参数是一个已经存在路劲名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。

mkfifoat函数和mkfifo函数相似。但是mkfifoat函数可以被用来在fd文件描述符表示的目录相关的位置创建一个FIFO。


FIFO不同于无名管道之处在于它提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO不相关的进程也能交换数据。值的注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。

注意:有名管道的名字存在于文件系统中,内容存放在内存中。

有名管道的打开规则


有名管道比无名管道多了一个打开操作:open


FIFO的打开规则:



如果当前打开操作时为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。


如果当前打开操作时为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENIO错误(当期打开操作没有设置阻塞标志)。


探究发现,如果open时没有使用O_NONBLOCK参数,我们发现不论读端还是写端先打开,先打开者都会阻塞,一直阻塞到另一端打开。


如果指定了O_NONBLOCK,则只读open立即返回。但是,如果没有进程为读而打开一个FIFO,那么只写open将返回-1,并将errno设置成ENXIO.


有名管道的读写规则


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信号(默认终止进程);

向有名管道中发送数据的进程源代码如下:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>

#define  FIFO_NAME  "/tmp/my_fifo"	//要创建的有名管道路径

int main(int argc, char *argv[])
{
	int pipe_fd;
	int res;
	char buffer[] = "hello world!";
	if (access(FIFO_NAME, F_OK) == -1)	//文件是否存在
	{
		res = mkfifo(FIFO_NAME, 0766);	//创建管道
		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, O_WRONLY);					//打开有名管道
	printf("the file's descriptor is %d\n", pipe_fd);
	
	if(pipe_fd != -1)
	{
		res = write(pipe_fd, buffer, sizeof(buffer));		//写数据
		if(res == -1)
		{
			fprintf(stderr, "Write error on pipe\n");
			exit(EXIT_FAILURE);
		}
		printf("write data is %s, %d bytes is write\n", buffer, res); //打印写入的数据及数据量
		(void)close(pipe_fd);					 //关闭管道
	}
	else
		exit(EXIT_FAILURE);
	printf("Process %d finished\n", getpid());			
	exit(EXIT_SUCCESS);
}



读进程负责从有名管道中读取数据,其源代码如下:
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#define FIFO_NAME  "/tmp/my_fifo"

int main(int argc, char *argv[])
{
	int pipe_fd;
	int res;
	char buffer[4096];
	int bytes_read = 0;
	bzero(buffer, sizeof(buffer));
	printf("Process %d opening FIFO O_RDONLY\n", getpid());
	pipe_fd = open(FIFO_NAME, O_RDONLY);	//打开管道,因创建在写进程,故执行时需要执行写进程

	printf("the file's descriptor is %d\n", pipe_fd);
	if (pipe_fd != -1)
	{
		bytes_read = read(pipe_fd, buffer, sizeof(buffer));	//读数据输出
		printf("the read data is %s\n", buffer);
		close(pipe_fd);						//关闭
	}
	else
		exit(EXIT_FAILURE);
	printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
	exit(EXIT_SUCCESS);
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值