Linux进程间通信(三):匿名管道 popen()、pclose()、pipe()、close()、dup()、dup2()
在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值。这里将介绍另一种进程间通信的方式——匿名管道,通过它进程间可以交换更多有用的数据。
一、什么是管道
如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号“|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入。
举个例子,在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。
二、使用popen()函数
1、popen()函数和pclose()函数介绍
有静就有动,有开就有关,与此相同,与popen()函数相对应的函数是pclose()函数,它们的原型如下:
#include <stdio.h>
FILE* popen(const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);
poen()函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。command是要运行的程序名和相应的参数。open_mode只能是"r(只读)"和"w(只写)"的其中之一。注意,popen()函数的返回值是一个FILE类型的指针,而Linux把一切都视为文件,也就是说我们可以使用stdio I/O库中的文件处理函数来对其进行操作。
如果open_mode是"r",主调用程序就可以使用被调用程序的输出,通过函数返回的FILE指针,就可以能过stdio函数(如fread)来读取程序的输出;如果open_mode是"w",主调用程序就可以向被调用程序发送数据,即通过stdio函数(如fwrite)向被调用程序写数据,而被调用程序就可以在自己的标准输入中读取这些数据。
pclose()函数用于关闭由popen创建出的关联文件流。pclose()只在popen启动的进程结束后才返回,如果调用pclose()时被调用进程仍在运行,pclose()调用将等待该进程结束。它返回关闭的文件流所在进程的退出码。
2、例子
很多时候,我们根本就不知道输出数据的长度,为了避免定义一个非常大的数组作为缓冲区,我们可以以块的方式来发送数据,一次读取一个块的数据并发送一个块的数据,直到把所有的数据都发送完。下面的例子就是采用这种方式的数据读取和发送方式。源文件名为popen.c,代码如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h> int main()
{
FILE *read_fp = NULL;
FILE *write_fp = NULL;
char buffer[BUFSIZ + 1];
int chars_read = 0; // 初始化缓冲区
memset(buffer, '\0', sizeof(buffer)); // 打开ls和grep进程
read_fp = popen("ls -l", "r");
write_fp = popen("grep rwxrwxr-x", "w"); // 两个进程都打开成功
if (read_fp && write_fp)
{
// 读取一个数据块
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
while (chars_read > 0)
{
buffer[chars_read] = '\0'; // 把数据写入grep进程
fwrite(buffer, sizeof(char), chars_read, write_fp); // 还有数据可读,循环读取数据,直到读完所有数据
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
} // 关闭文件流
pclose(read_fp);
pclose(write_fp); exit(EXIT_SUCCESS);
} exit(EXIT_FAILURE);
}
运行结果如下:
从运行结果来看,达到了信息筛选的目的。程序在进程ls中读取数据,再把数据发送到进程grep中进行筛选处理,相当于在shell中直接输入命令:ls -l | grep rwxrwxr-x。
3、popen()的实现方式及优缺点
当请求popen()调用运行一个程序时,它首先启动shell,即系统中的sh命令,然后将command字符串作为一个参数传递给它。
这样就带来了一个优点和一个缺点。优点是:在Linux中所有的参数扩展都是由shell来完成的。所以在启动程序(command中的命令程序)之前先启动shell来分析命令字符串,也就可以使各种shell扩展(如通配符)在程序启动之前就全部完成,这样我们就可以通过popen()启动非常复杂的shell命令。
而它的缺点就是:对于每个popen()调用,不仅要启动一个被请求的程序,还要启动一个shell,即每一个popen()调用将启动两个进程,从效率和资源的角度看,popen()函数的调用比正常方式要慢一些。
三、pipe()调用
如果说popen()是一个高级的函数,pipe()则是一个底层的调用。与popen()函数不同的是,它在两个进程之间传递数据不需要启动一个shell来解释请求命令,同时它还提供对读写数据的更多的控制。
pipe()函数的原型如下:
#include <unistd.h>
int pipe(int file_descriptor[2]);
我们可以看到pipe()函数的定义非常特别,该函数在数组中墙上两个新的文件描述符后返回0,如果返回返回-1,并设置errno()来说明失败原因。
数组中的两个文件描述符以一种特殊的方式连接起来,数据基于先进先出的原则,写到file_descriptor[1]的所有数据都可以从file_descriptor[0]读回来。由于数据基于先进先出的原则,所以读取的数据和写入的数据是一致的。
特别提醒:
1、从函数的原型我们可以看到,它跟popen函数的一个重大区别是,popen()函数是基于文件流(FILE)工作的,而pipe是基于文件描述符工作的,所以在使用pipe后,数据必须要用底层的read()和write()调用来读取和发送。
2、不要用file_descriptor[0]写数据,也不要用file_descriptor[1]读数据,其行为未定义的,但在有些系统上可能会返回-1表示调用失败。数据只能从file_descriptor[0]中读取,数据也只能写入到file_descriptor[1],不能倒过来。
例子:
首先,我们在原先的进程中创建一个管道,然后再调用fork()创建一个新的进程,最后通过管道在两个进程之间传递数据。源文件名为pipe.c,代码如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h> int main(int argc, char **argv)
{
ssize_t data_processed = 0;
int filedes[2];
const char data[] = "Hello pipe!\n";
char buffer[BUFSIZ + 1];
pid_t pid; // 清空缓冲区
memset(buffer, '\0', sizeof(buffer)); if (pipe(filedes) == 0)
{
// 创建管道成功
// 调用fork 创建子进程
pid = fork();
if (pid == -1)
{
fprintf(stderr, "Fork failure\n");
exit(EXIT_FAILURE);
} if (pid == 0)
{
// 子进程
// 读取数据
data_processed = read(filedes[0], buffer, BUFSIZ);
printf("Read %ld bytes: %s\n", data_processed, buffer);
exit(EXIT_SUCCESS);
}
else
{
// 父进程
// 写数据
data_processed = write(filedes[1], data, strlen(data));
printf("Wrote %ld bytes: %s\n", data_processed, data); // 休眠2秒, 主要是为了等待子进程结束, 这样做也只是纯粹为了输出好看而已
// 父进程其实没有必要等待子进程结束
sleep(2);
exit(EXIT_SUCCESS);
}
}
else
{
fprintf(stderr, "Pipe failure\n");
exit(EXIT_FAILURE);
}
}
运行结果为:
可见,子进程读取了父进程写到filedes[1]中的数据,如果在父进程中没有sleep()语句,父进程可能在子进程结束前结束,这样你可能将看到两个输入之间有一个命令提示符分隔。
四、把管道用作标准输入和标准输出
下面来介绍一种用管道来连接两个进程的更简洁方法,我们可以把文件描述符设置为一个已知值,一般是标准输入0或标准输出1。这样做最大的好处是可以调用标准程序,即那些不需要以文件描述符为参数的程序。
为了完成这个工作,我们还需要两个函数的辅助,它们分别是dup函数或dup2函数,它们的原型如下:
#include <unistd.h>
int dup(int file_descriptor);
int dup2(int file_descriptor_one, int file_descriptor_two);
dup调用创建一个新的文件描述符与作为它的参数的那个已有文件描述符指向同一个文件或管道。对于dup()函数而言,新的文件描述总是取最小的可用值。而dup2()所创建的新文件描述符或者与int file_descriptor_two相同,或者是第一个大于该参数的可用值。所以当我们首先关闭文件描述符0后调用dup(),那么新的文件描述符将是数字0。
例子
在下面的例子中,首先打开管道,然后fork()一个子进程,然后在子进程中,使标准输入指向读管道,然后关闭子进程中的读管道和写管道,只留下标准输入,最后调用execlp()函数来启动一个新的进程od,但是od并不知道它的数据来源是管道还是终端。父进程则相对简单,它首先关闭读管道,然后在写管道中写入数据,再关闭写管道就完成了它的任务。源文件为pipe2.c,代码如下:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h> int main(int argc, char **argv)
{
ssize_t data_proccessed = 0;
int pipes[2];
const char data[] = "123";
pid_t pid; if (pipe(pipes) == 0)
{
pid = fork();
if (pid == -1)
{
fprintf(stderr, "Fork failure\n");
exit(EXIT_FAILURE);
} if (pid == 0)
{
// 子进程中
// 使标准输入指向 filedes[0]
close(0);
dup(pipes[0]); // 关闭 pipes[0] 和 pipes[1], 只剩下标准输入
close(pipes[0]);
close(pipes[1]); // 启动新进程od
execlp("od", "od", "-c", 0);
exit(EXIT_FAILURE);
}
else
{
// 关闭 pipes[0], 因为父进程不用读取数据
close(pipes[0]);
data_proccessed = write(pipes[1], data, strlen(data)); // 写完数据后, 关闭 pipes[1]
close(pipes[1]);
printf("%d -Worte %ld bytes\n", getpid(), data_proccessed);
}
}
else
{
fprintf(stderr, "Pipe failure\n");
exit(EXIT_FAILURE);
}
}
运行结果为:
从运行结果中可以看出od进程正确地完成了它的任务,与在shell中直接输入od -c和123的效果一样。
五、关于管道关闭后的读操作的讨论
现在有这样一个问题,假如父进程向管道file_pipe[1]写数据,而子进程在管道file_pipe[0]中读取数据,当父进程没有向file_pipe[1]写数据时,子进程则没有数据可读,则子进程会发生什么呢?再者父进程把file_pipe[1]关闭了,子进程又会有什么反应呢?
当写数据的管道没有关闭,而又没有数据可读时,read()调用通常会阻塞,但是当写数据的管道关闭时,read()调用将会返回0而不是阻塞。注意,这与读取一个无效的文件描述符不同,read()一个无效的文件描述符返回-1。
六、匿名管道的缺陷
看了这么多相信大家也知道它的一个缺点,就是通信的进程,它们的关系一定是父子进程的关系,这就使得它的使用受到了一点的限制,但是我们可以使用命名管道来解决这个问题。命名管道将在下一篇文章:Linux进程间通信——使用命名管道 中介绍。
参考:
http://blog.csdn.net/ljianhui/article/details/10168031
http://blog.csdn.net/zhouqi_2011/article/details/7039157
《Linux 高性能服务器编程》
Linux进程间通信(三):匿名管道 popen()、pclose()、pipe()、close()、dup()、dup2()的更多相关文章
- Linux进程间通信——使用匿名管道
在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值.这里将介绍另一种进程间通信的方式——匿名管道,通过它进程间可以交换更多有用的数据. 一 ...
- Linux进程间通信——使用命名管道
在前一篇文章——Linux进程间通信——使用匿名管道中,我们看到了如何使用匿名管道来在进程之间传递数据,同时也看到了这个方式的一个缺陷,就是这些进程都由一个共同的祖先进程启动,这给我们在不相关的的进程 ...
- 练习--LINUX进程间通信之无名管道PIPE
IBM上放的这个系统不错,刚好可以系统回温一下LINUX的系统知识. http://www.ibm.com/developerworks/cn/linux/l-ipc/part1/ 感觉年纪大了,前几 ...
- Linux进程通信----匿名管道
Linux进程通信中最为简单的方式是匿名管道 匿名管道的创建需要用到pipe函数,pipe函数参数为一个数组表示的文件描述字.这个数组有两个文件描 述字,第一个是用于读数据的文件描述符第二个是用于写数 ...
- Linux进程间通信(三) - 信号
什么是信号 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件.在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的.信号是进程间通 ...
- 《Linux 进程间通信》命名管道:FIFO
命名管道的主要用途:不相关的进程之间交换数据. 命令行上创建命名管道: $ mkfifo filename 程序中创建命名管道: #include <sys/types.h> #incl ...
- Linux进程间通信(四):命名管道 mkfifo()、open()、read()、close()
在前一篇文章—— Linux进程间通信 -- 使用匿名管道 中,我们看到了如何使用匿名管道来在进程之间传递数据,同时也看到了这个方式的一个缺陷,就是这些进程都由一个共同的祖先进程启动,这给我们在不相关 ...
- IPC——匿名管道
Linux进程间通信——使用匿名管道 在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值.这里将介绍另一种进程间通信的方式——匿名管道,通过它 ...
- linux的IPC进程通信方式-匿名管道(一)
linux的IPC进程通信-匿名管道 什么是管道 如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号"|"来使用管道,但是管道的真正定义是 ...
随机推荐
- POJ2942 Knights of the Round Table[点双连通分量|二分图染色|补图]
Knights of the Round Table Time Limit: 7000MS Memory Limit: 65536K Total Submissions: 12439 Acce ...
- 第3章 Linux常用命令(2)_权限管理命令
2. 权限管理命令 2.1 改变文件或目录权限:chmod (1)chmod命令 命令名称 chmod(change the permission mode of a file) 命令所在路径 /bi ...
- 【C#】【Thread】SpinWait
System.Threading.SpinWait 是一个轻量同步类型,可以在低级别方案中使用它来避免内核事件所需的高开销的上下文切换和内核转换. 在多核计算机上,当预计资源不会保留很长一段时间时,如 ...
- Linux 进程间通讯详解六
ftok()函数 key_t ftok(const char *pathname, int proj_id); --功能:创建系统建立IPC通讯 (消息队列.信号量和共享内存) 时key值 --参数 ...
- Android SQLITE 操作工具类
首先创建一个类 DatabaseHelper 继承SQLiteOpenHelper帮助类,定义数据库版本,数据库名称,创建表名. private static final int DATABASE_V ...
- MVC5 + EF6 + Bootstrap3 (16) 客户端验证
Slark.NET-博客园 http://www.cnblogs.com/slark/p/mvc5-ef6-bs3-get-started-client-side-validation.html 系列 ...
- 转 android 从底部弹出一个popuwindow,渐入渐出效果。我这里是用在购物车需要选择购买选项的操作。
最近要改客户端,需要实现一个从底部弹出的popuwindow,像我这种渣渣android技术,能整出popuwindow但是整不出动画,百度之,记录一下. 从下面这个地址转的 http://blog. ...
- Android开发自学笔记(Android Studio)—4.2TextView及其子类
一.引言 TextView是我们最常用的一个控件了,它类似于C# Winform程序中的Lable,Java Swing编程中的JLable,不过相对功能更强大些,但从功能上看,它其实就是个文字编辑器 ...
- nginx.conf中配置laravel框架站点
nginx.conf配置如下: user nginx nginx;worker_processes 4; error_log logs/error.log error; pid logs/nginx. ...
- JavaScript自运行函数(function(){})()的理解
今天打开JQuery源文件(jquery-1.8.3), 看到JQuery的初始化过程是这样的 (function( window, undefined ) { // .... })( window ...