进程通信之无名管道

本节学习进程通信的另一种方式:管道。管道是一个进程连接数据流到另一个进程的通道,它通常把一个进程的输出通过管道连接到另一个进程的输入。在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

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 无名管道(Anonymous Pipe)是操作系统中用于实现进程间通信(IPC)的一种机制,Windows操作系统也支持使用无名管道进行进程间通信。 在Windows操作系统中,无名管道是一种特殊类型的文件,只能用于相关进程间通信,无法用于不相关进程间通信无名管道通常是单向的,有一个读取端(Read end)和一个写入端(Write end),一个进程可以往管道的写入端写入数据,另一个进程可以从管道的读取端读取数据。 无名管道常用于兄弟进程间通信(如父子进程)或者由同一个进程创建的两个进程间通信。 使用无名管道进行进程间通信的步骤如下: 1. 创建管道:调用CreatePipe函数创建一个无名管道,返回读取端和写入端句柄。 2. 创建进程:使用CreateProcess函数创建需要进行通信的进程。 3. 设置进程的标准输入输出:使用SetStdHandle函数将管道的读取端和写入端句柄分别设置为所创建进程的标准输入和标准输出。 4. 进程通信:通过写入端句柄向管道写入数据,然后通过读取端句柄从管道读取数据。 在这个过程中,写入端和读取端可以位于同一个进程中,也可以位于不同的进程中。写入端写入的数据会被读取端读取,实现了进程间的通信无名管道可以用于传输任意类型的数据,包括文本、二进制等。只要进程通过读取端和写入端的兼容接口进行数据的读写操作,就可以实现进程间的通信。 总之,使用无名管道是一种简单有效的方式来实现Windows操作系统中进程间的通信,通过读取端和写入端的数据传输,可以实现数据的共享和交换,满足进程间通信的需求。 ### 回答2: 无名管道(Anonymous Pipes)是一种在Windows系统中用于实现进程间通信的机制。它通常用于在父子进程之间或者兄弟进程之间进行数据传输。 在使用无名管道之前,我们需要创建一个匿名的管道对象,它定义了一个读取端和一个写入端。父进程可以通过创建管道对象,并将写入端传递给子进程,来实现进程间通信。子进程可以通过读取从父进程写入端传来的数据来获取信息。 无名管道的使用步骤如下: 1. 使用CreatePipe函数创建一个无名管道,该函数返回两个句柄(一个读句柄和一个写句柄),分别用于读取和写入管道中的数据。 2. 通常,我们会在父进程中调用CreateProcess函数创建一个子进程。子进程会继承父进程的句柄。 3. 在创建子进程后,父进程可以关闭子进程中不需要的句柄(写句柄),子进程可以关闭父进程中不需要的句柄(读句柄)。 4. 当父进程想要向子进程发送数据时,它可以使用WriteFile函数来向写入端写入数据。 5. 子进程可以使用ReadFile函数从读取端读取父进程写入的数据。 通过这种方式,父进程和子进程之间可以进行双向的通信。 需要注意的是,无名管道只适用于具有亲密关系(比如父子进程)或具有共同祖先(比如兄弟进程)的进程之间。此外,无名管道是半双工的,意味着只能在一个方向上传递数据。如果需要实现双向通信,可以使用两个无名管道来分别实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值