© / Posted in Linux / August 18, 2007
——————
问题和现有方法
Linux提供了popen和pclose函数 ,用于创建和关闭管道与另外一个进程进行通信。其接口如下:
FILE *popen(const char *command, const char *mode);
int pclose(FILE *stream);
遗憾的是,popen创建的管道只能是单向的–mode只能是 “r” 或 “w” 而不能是某种组合–用户只能选择要么往里写,要么从中读,而不能同时在一个管道中进行读写。实际应用中,经常会有同时进行读写的要求,比如,我们可能希望把文本数据送往sort工具排序后再取回结果。此时popen就无法用上了。我们需要寻找其它的解决方案。
有一种解决方案是使用pipe函数 创建两个单向管道。没有错误检测的代码示意如下:
int pipe_in[2], pipe_out[2];
pid_t pid;
pipe(&pipe_in); // 创建父进程中用于读取数据的管道
pipe(&pipe_out); // 创建父进程中用于写入数据的管道
if ( (pid = fork()) == 0) { // 子进程
close(pipe_in[0]); // 关闭父进程的读管道的子进程读端
close(pipe_out[1]); // 关闭父进程的写管道的子进程写端
dup2(pipe_in[1], STDOUT_FILENO); // 复制父进程的读管道到子进程的标准输出
dup2(pipe_out[0], STDIN_FILENO); // 复制父进程的写管道到子进程的标准输入
close(pipe_in[1]); // 关闭已复制的读管道
close(pipe_out[0]); // 关闭已复制的写管道
/* 使用exec执行命令 */
} else { // 父进程
close(pipe_in[1]); // 关闭读管道的写端
close(pipe_out[0]); // 关闭写管道的读端
/* 现在可向pipe_out[1]中写数据,并从pipe_in[0]中读结果 */
close(pipe_out[1]); // 关闭写管道
/* 读取pipe_in[0]中的剩余数据 */
close(pipe_in[0]); // 关闭读管道
/* 使用wait系列函数等待子进程退出并取得退出代码 */
}
当然,这样的代码的可读性(特别是加上错误处理代码之后)比较差,也不容易封装成类似于popen/pclose的函数,方便高层代码使用。究其原因,是pipe函数返回的一对文件描述符只能从第一个中读、第二个中写(至少对于Linux是如此)。为了同时读写,就只能采取这么累赘的两个pipe调用、两个文件描述符的形式了。
新方法
使用pipe就只能如此了。不过,Linux实现了一个源自BSD的socketpair调用 ,可以实现上述在同一个文件描述符中进行读写的功能(该调用目前也是POSIX规范的一部分 )。该系统调用能创建一对已连接的(UNIX族)无名socket。在Linux中,完全可以把这一对socket当成pipe返回的文件描述符一样使用,唯一的区别就是这一对文件描述符中的任何一个都可读和可写。
这已经接近了我们想要的目标了。但仍然存在一个问题,阻碍我们使用socketpair实现一个管道与子进程通信:为了解决我前面的提出的使用sort的应用问题,我们需要关闭子进程的标准输入通知子进程数据已经发送完毕,而后从子进程的标准输出中读取数据直到遇到EOF。使用两个单向管道的话每个管道可以单独关闭,因而不存在此问题;而在使用双向管道时,如果不关闭管道就无法通知对端数据已经发送完毕,但关闭了管道又无法从中读取结果数据。–这一问题不解决的话,使用socketpair的设想就变得毫无意义。
在查找和试验之后,我发现shutdown调用 可解决此问题。毕竟socketpair产生的文件描述符是一对socket,socket上的标准操作都可以使用,其中也包括shutdown。–利用shutdown,可以实现一个半关闭操作,通知对端本进程不再发送数据,同时仍可以利用该文件描述符接收来自对端的数据。没有错误检测的代码示意如下:
int fd[2];
pid_t pid;
socketpair(AF_UNIX, SOCKET_STREAM, 0, fd); // 创建管道
if ( (pid = fork()) == 0) { // 子进程
close(fd[0]); // 关闭管道的父进程端
dup2(fd[1], STDOUT_FILENO); // 复制管道的子进程端到标准输出
dup2(fd[1], STDIN_FILENO); // 复制管道的子进程端到标准输入
close(fd[1]); // 关闭已复制的读管道
/* 使用exec执行命令 */
} else { // 父进程
close(fd[1]); // 关闭管道的子进程端
/* 现在可在fd[0]中读写数据 */
shutdown(fd[0], SHUT_WR); // 通知对端数据发送完毕
/* 读取剩余数据 */
close(fd[0]); // 关闭管道
/* 使用wait系列函数等待子进程退出并取得退出代码 */
}
很清楚,这比使用两个单向管道的方案要简洁不少。我将在此基础上作进一步的封装和改进。
封装和实现
直接使用上面的方法,无论怎么看,至少也是丑陋和不方便的。程序的维护者想看到的是程序的逻辑,而不是完成一件任务的各种各样的繁琐细节。我们需要一个好的封装。
封装可以使用C或者C++。此处,我按照UNIX的传统,提供一个类似于POSIX标准中popen/pclose函数调用的C封装,以保证最大程度的可用性。接口如下:
FILE *dpopen(const char *command);
int dpclose(FILE *stream);
int dphalfclose(FILE *stream);
关于接口,以下几点需要注意一下:
与pipe函数类似,dpopen返回的是文件结构的指针,而不是文件描述符。这意味着,我们可以直接使用fprintf之类的函数,文件缓冲区会缓存写入管道的数据(除非使用setbuf函数关闭文件缓冲区),要保证数据确实写入到管道中需要使用fflush函数。
由于dpopen返回的是可读写的管道,所以popen的第二个表示读/写的参数不再需要。
在双向管道中我们需要通知对端写数据已经结束,此项操作由dphalfclose函数来完成。
具体的实现请直接查看程序源代码,其中有详细的注释和doxygen文档注释 。我只略作几点说明:
本实现使用了一个链表来记录所有dpopen打开的文件指针和子进程ID的对应关系,因此,在同时用dpopen打开的管道的多的时候,dpclose(需要搜索链表)的速度会稍慢一点。我认为在通常使用过程中这不会产生什么问题。如果在某些特殊情况下这会是一个问题的话,可考虑更改dpopen的返回值类型和dpclose的传入参数类型(不太方便使用,但实现简单),或者使用哈希表/平衡树来代替目前使用的链表以加速查找(接口不变,但实现较复杂)。
当编译时在gcc中使用了”-pthread”命令行参数时,本实现会启用POSIX线程支持,使用互斥量保护对链表的访问。因此本实现可以安全地用于POSIX多线程环境之中。
与popen类似 ,dpopen会在fork产生的子进程中关闭以前用dpopen打开的管道。
如果传给dpclose的参数不是以前用dpopen返回的非NULL值,当前实现除返回-1表示错误外,还会把errno设为EBADF。对于pclose而言,这种情况在POSIX规范中被视为不确定(unspecified)行为 。
实现中没有使用任何平台相关特性,以方便移植到其它POSIX平台上。
下面的代码展示了一个简单例子,将多行文本送到sort中,然后取回结果、显示出来:
#include
#include
#include “dpopen.h”
#define MAXLINE 80
int main()
{
char line[MAXLINE];
FILE *fp;
fp = dpopen(”sort”);
if (fp == NULL) {
perror(”dpopen error”);
exit(1);
}
fprintf(fp, “orange\n”);
fprintf(fp, “apple\n”);
fprintf(fp, “pear\n”);
if (dphalfclose(fp) < 0) {
perror(”dphalfclose error”);
exit(1);
}
for (;;) {
if (fgets(line, MAXLINE, fp) == NULL)
break;
fputs(line, stdout);
}
dpclose(fp);
return 0;
}
输出结果为:
apple
orange
pear
总结
本文阐述了一个使用socketpair系统调用在Linux上实现双向进程通讯管道的方法,并提供了一个实现。该实现提供的接口与POSIX规范中的popen/pclose函数较为接近,因而非常易于使用。该实现没有使用平台相关的特性,因而可以不加修改或只进行少量修改即可移植到支持socketpair调用的POSIX系统中去。
*******************
我
welcome to our burberry outlet store ,there are many fashion and hight quality burberry bags wait for
you!
do you want to buy burberry bags for yourslf or your beat friends in sometimes?
please come to our burberry outlet store.yle burberry watches for you.do
our burberry outlet store offer many new st
not miss the fashion burbery Burberry T-shirts.
In burberry sunglasses stores it really is typically some sort of
most effective style which precisely the top notch and several middle-class are typically in a region to afford
Fouthy-six Putting on a new burberry watches for men and relish the
summertime Does one prefers to invest in Burberry bags?
as we all know the burberry brand is very famouse in the world.in burberry bags outlet store you
will have a good time!
there is new store,the name is burberry outlet !you are interested in new store,you will have a good
time in there.
If you use burberry bags ,you will feel very Summer feeling, so many girls put it
down.
evey women love the burberry shirts in life,the burberry can give you more
confidence in sometimes.
Jack bought the burberry sunglasses and the next day he came to the
interview in the Burberry cheak shirt.
Join forums that areparticularlymeant to talk about watches. If you are searching for burberry sale for women, then
obviouslycheck out women's forum.
One of the most popular British exports is the burberry outlet online .
The particular burberry bags typically reveal the actual Burberry renowned examine layout
which has been made popular by the garment range.
The color of burberry ties are bright but with the characters of mature and steady
the burberry shoes collection had occipied a large markey share and enjoys a massive
fan following.
I believe one's destiny of your burberry wallets long decrease jackets are
often more large-scale progress.