进程通信之管道

本节学习进程通信的另一种方式:管道。管道是一个进程连接数据流到另一个进程的通道,它通常把一个进程的输出通过管道连接到另一个进程的输入。在shell命令中经常会看到管道的应用,比如我们要列出当前文件下所有命名中有"test"的文件:ls -l | grep test。其中"|"就代表我们在使用管道,它会把"ls -l"的查询结果通过管道,发送给grep,然后执行"grep test"命令后把结构输出到终端。总之一句话:管道会把一个进程的输出数据,发送给另一进程,作为另一个进程的输入数据。

http://blog.csdn.net/xiaoliangsky/article/details/40112729

一 无名管道

1 无名管道的特点

1)只能用于具有亲缘关系的进程之间,父子进程,兄弟进程之间通信,因为只有子进程才能继续父进程的文件描述符。

2)半双共通信(同一时刻只能对管道进行一种操作(读操作或者写操作)),具有读端口和写端口。

3)管道是一种特殊的文件,可以使用文件io函数(read,write...)来操作,但不能使用lseek函数来定位操作。

4)管道是在内存中,不用我们主动区删除。

5)管道是基于队列实现的,有大小限制。

2 pipe函数

int pipe(int pipefd[2]);

pipe函数创建了一个单向数据通道,这个通道可以用来在进程之间通信。

pipefd: pipefd数组用来返回两个文件描述符,这两个文件描述符代表了通道的两端。pipefd[0]代表通道的读取端,pipefd[1]代表了通道的写入段。在读取端读取数据,在输写入端口写入数据。

返回值

函数调用成功返回0

调用失败返回-1,这时errno存储错误码。

错误码

EFAULT pipefd数组地址不合法

EMFILE 进程使用了太多的文件描述符(文件描述符使用达到上限)

ENFILE 系统已无文件描述符可用

注意:

1)不要使用pipefd[0]来写数据,也不要使用pipefd[1]来读取数据,否则其行为是未定义的。

2)在用pipefd[0]来读数据时,要先关闭pipefd[1];在用pipefd[1]来写入数据时,要关闭pipefd[0]。

下面来看父子进程通过管道通信的一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define  MAXLINE  100

void err_sys(const char *str);

int main()
{
	int      n;
	int      status;
	int      fd[2];
	pid_t    pid;
	char     line[MAXLINE];

	if (pipe(fd) < 0)
	{
		err_sys("pipe error\n");
	}

	if ((pid = fork()) < 0)
	{
		err_sys("fork error\n");
	}
	else if (pid == 0)
	{
		close(fd[0]);
		if (write(fd[1], "hello world\n", sizeof("hello world\n")) == -1)
		{
			err_sys("write error\n");

			exit(-1);
		}
	}
	else
	{
		if (waitpid(pid, &status, 0) != pid)
		{
			err_sys("waitpid error\n");
		}

		if (WIFEXITED(status) == 0)
		{
			err_sys("child process exit failed\n");
		}

		close(fd[1]);
		n = read(fd[0], line, MAXLINE);
		write(STDOUT_FILENO, line, n);
	}

	return 0;
}

void err_sys(const char *str)
{
	printf("%s", str);
}

运行结果如下:

3 popen函数

FILE *popen(const char *command, const char *type)

int   pclose(FILE *stream);

popen函数用创建管道的方式启动一个进程,并调用shell来执行command命令。由于管道是单向的,所以type的方式只能是只读或者只写,不能同时读写。返回结果流也相应的是只写或者只读。

command 参数是一个字符串指针,指向一个以null结束的字符串,这个字符串包含一个shell命令. 这个命令被送到/bin/sh以 -c 参数执行, 即由shell来执行。

type 参数也是一个指向以null结束符结尾的字符串的指针, 这个字符串必须是‘r‘或者‘w’来指明是读还是写。

popen()函数的返回值是一个普通的标准I/O流, 它只能用pclose()函数来关闭, 而不是fclose()函数。向这个流的写入被转化为对command命令的标准输入; 而command命令的标准输出则是和调用popen(), 函数的进程相同,除非这个被command命令自己改变;相反的, 读取一个“被popen了的” 流, 就相当于读取command命令的标准输出, 而command的标准输入则是和调用popen函数的进程相同。可以概括为:向这个流写入数据就是command的输入;从这个流读取就是command的输出。注意,
popen函数的输出流默认是被全缓冲的。

pclose函数用于关闭由popen创建出的关联文件流。pclose只在popen启动的进程结束后才返回,如果调用pclose时被调用进程仍在运行,pclose调用将等待该进程结束,并返回一个command命令的退出状态。

stream是popen函数返回的文件流。

返回值

popen 如果fork或pipe函数调用失败,或者popen函数不能申请没存,则函数返回NULL。

pclose 函数调用失败会返回-1。

错误码

popen函数因为内存错误就不会设置errno,fork或pipe错误就会相应的设置errno;type参数无效,errno会被设置为EINVAL。

pclose无法获得子进程状态,errno会被设置为ECHILD。

popen函数的优缺点

优点:在Linux中所有的参数扩展都是由shell来完成的。所以在启动程序(command中的命令程序)之前先启动shell来分析命令字符串,也就可以使各种shell扩展(如通配符)在程序启动之前就全部完成,这样我们就可以通过popen启动非常复杂的shell命令。

缺点:对于每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每一个popen调用将启动两个进程,从效率和资源的角度看,popen函数的调用比正常方式要慢一些。

下面来看两个列子,以便跟深入的了解popen函数。

第一个列子

用popen来实现一个程序,在管道中让用户输入数据,然后在从管道中取出用户输出的数据,显示在终端。首先实现一个输入程序input.c,然后在testinput.c中用popen调用这个程序,把input的输出作为testinput的输入。

input.c的代码:

#include <string.h>
#include <stdio.h>
#include <ctype.h>

int main()
{
	int  c;
	//用户输入数据
	while ((c = getchar()) != EOF)
	{
		if (isupper(c))
		{
			c = tolower(c);
		}
		//输出数据作为testinput的输入数据
		if (putchar(c) == EOF)
		{
			fputs("output error\n", stdout);
		}

		if (c == '\n')
		{
			fflush(stdout);
		}
	}

	return 0;
}

testinput.c的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAXLINE 100

int main()
{
	char   line[MAXLINE];
	FILE  *fpin;

	//popen的第一个参数是命令,这里执行一个可执行文件
	if ((fpin = popen("./input", "r")) == NULL)
	{
		fputs("popen error\n", stdout);
	}

	for ( ; ; )
	{
		fputs("prompt> ", stdout);
		fflush(stdout);

		if (fgets(line, MAXLINE, fpin) == NULL)
		{
			break;
		}

		if (fputs(line, stdout) == EOF)
		{
			fputs("fputs error to pipe", stdout);
		}
	}

	if (pclose(fpin) == -1)
	{
		fputs("pclose error\n", stdout);
	}

	puts("");

	exit(0);
}

运行结果如下:

第二个列子很简单,就是调用两次popen函数,第一次执行“ls -l”命名,它的输出作为第二次命令”grep .c“的输入。

代码:

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

#define MAXSIZE 200

void err_sys(const char *str);

int main()
{
	FILE    *fpin;
	FILE    *fpout;
	char     line[MAXSIZE+1];

	memset(line, '\0', sizeof(line));

	fpin  = popen("ls -l", "r");
	if (fpin == NULL)
	{
		err_sys("popen fpin error\n");

		return -1;
	}

	fpout = popen("grep .c", "w");
	if (fpout == NULL)
	{
		err_sys("popen fpout error\n");
		pclose(fpin);

        return -1;
	}
	//fgets失败或者读到文件未返回NULL
	while (fgets(line, MAXSIZE, fpin) != NULL)//从第一次中取出数据
	{	//fputs成功返回非负,失败返回EOF
		if (fputs(line, fpout) == EOF)//将取出的数据作为第二次命令的输入
		{
			err_sys("fputs error\n");
		}
	}

	pclose(fpin);
    pclose(fpout);

	return 0;
}

void err_sys(const char *str)
{
	printf("%s", str);
}

运行结果:

http://blog.csdn.net/xiaoliangsky/article/details/40112729

时间: 2024-08-26 20:21:25

进程通信之管道的相关文章

进程通信——匿名管道

有的时候在程序的开发过程中,两个进程之间会有数据的交互.信号的机制能够实现进程通信.它通过 “中断--响应” 的异步模式来工作.但作为通信来讲,信号还是远远不够的.因为它不能够携带任何其他的信息.只利用信号机制来实现进程通信显得捉襟见肘,并且信号的优势并不此.所以必须开发新的进程间通信的方法.本文所学习的 "匿名管道" 便是其中的一种最简单的方法. 基本概念 在讲管道的基本概念之前,首先提一个问题.如果让你来设计进程通信的方式,你会怎么做?最简单的方式莫过于两个进程打开同一个文件,这样

进程通信-无名管道

无名管道:主要是针对进程通信的(自己感觉它有很大的局限性) 特点:它不是一个文件系统,不能按名访问,这也是它和有名管道之间最大的区别.无名管道只是一个系统内存里面的东西. 半双工模式,数据只能流向一个方向(老师给我们举得例子就是水厂的水到居民用水,不可能倒着流对吧). 进程之间通信,但是只能是有亲缘关系的进程才能进行通信?比如父子进程:因为在父子进程中,子进程拷贝父进程的数据段,这让这两个进程在通信的时候就有了相连的关系. 下面我们来看一下:无名管道的创建,写入,读取,关闭(我用的的是Liunx

Linux学习笔记(13)-进程通信|命名管道

匿名管道只能在具有亲属关系的进程间通信,那么如果想要在不具有亲戚关系,想在陌生人之间通信,那又该怎么办呢? 别慌,Linux身为世界上*强大的操作系统,当然提供了这种机制,那便是命名管道-- 所谓命名管道,那便是拥有名字的管道,同时也被称之为FIFO,谈到FIFO,那么做过单片机开发的同学想必是不陌生的. 在很多单片机的项目中,都使用过FIFO,FIFO其实是一种队列,先进先出,这样可以保证读出数据和写入数据的一致性. 使用FIFO文件,便可以在不同的,且不具有亲属关系的进程中进程通信. 创建命

linux进程通信之管道

1.介绍: 1)同一主机: unix进程通信方式:无名管道,有名管道,信号 system v方式:信号量,消息队列,共享内存 2)网络通信:Socket,RPC 2.管道: 无名管道(PIPE):使用一个进程的标准输出作为另一个进程的标准输入建立的一个单向管道,执行完成后消失.主要用于父进程与子进程之间,或者两个兄弟进程之间.采用的是单向 1)创建无名管道:(#include(unistd.h)) extern int pipe(int pipes[2]),pipes[0]完成读操作,pipes

Linux进程通信----匿名管道

Linux进程通信中最为简单的方式是匿名管道 匿名管道的创建需要用到pipe函数,pipe函数参数为一个数组 表示的文件描述字.这个数组有两个文件描述字,第一个是用 于读数据的文件描述符第二个是用于写数据的文件描述符. 不能将用于写的文件描述符进行读操作或者进行读的文件描述 符进写操作,这样都会导致错误. 关于匿名管道的几点说明: 1.匿名管道是半双工的,即一个进程只能读,一个进程只能写    要实现全双工,需要两个匿名管道. 2.只能在父子进程或者兄弟进程进行通信. 3.在读的时候关闭写文件描

Linux下进程通信之管道

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication).如下图所示. 目前进程通信的方式有: 管道 FIFO 消息队列 信号量 共享内存 套接字 管道 管道概念 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: 管道是半双工的,数

linux下的进程通信之管道

概念:管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条.管道的一端连接一个进程的输出.这个进程会向管道中放入信息.管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息. 优点:不需要加锁,基于字节流不需要定义数据结构 缺点:速度慢,容量有限,只能用于父子进程之间,使用场景狭窄 基本原理: 一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用.当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息.当管道被放满信息的时候,尝试放入信息的进程

进程通信——有名管道

上一篇文章中学习了进程间通信的一种简答的方法:匿名管道.但是它只能用于具有亲缘关系的进程之间的通信.而FIFO的通信机制与之相似,却可以在任意两个进程之间通信. FIFO文件操作 创建FIFO类似于创建文件,确实,FIFO可以存在于文件系统中.下面是创建FIFO的函数: #include <sys/stat.h> int mkfifi(const char *pathname, mode_t mode); 返回值:若成功返回0,若出错返回-1 参数说明: pathname: FIFO文件的路径

Linux 进程通信之管道

管道是单向的.先进先出的,它把一个进程的输出和另一个进程的输入连接在一起.一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据.数据被一个进程读出后,将被从管道中删除,其他读进程将不能再读到这些数据.管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞.同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞. 管道包括无名管道和有名管道两种,无名管道只能用于父进程和子进程间的通信,而有名管道可以用于同一系统中的任意两个进程间的通信. 无名管道由pipe()函数