分享
 
 
 

Linux 上实现双向进程间通信管道

王朝system·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

级别: 中级

吴咏炜 (adah@sh163.net

本文阐述了一个使用 socketpair 系统调用在 Linux 上实现双向进程通讯管道的方法,并提供了一个实现。

问题和常见方法

Linux 提供了 popen 和 pclose 函数(1) ,用于创建和关闭管道与另外一个进程进行通信。其接口如下:

FILE *popen(const char *command, const char *mode);

int pclose(FILE *stream);

遗憾的是,popen 创建的管道只能是单向的 -- mode 只能是 "r" 或 "w" 而不能是某种组合--用户只能选择要么往里写,要么从中读,而不能同时在一个管道中进行读写。实际应用中,经常会有同时进行读写的要求,比如,我们可能希望把文本数据送往 sort 工具排序后再取回结果。此时 popen 就无法用上了。我们需要寻找其它的解决方案。

有一种解决方案是使用 pipe 函数(2)创建两个单向管道。没有错误检测的代码示意如下:

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 调用 (3),可以实现上述在同一个文件描述符中进行读写的功能(该调用目前也是 POSIX 规范的一部分(4) )。该系统调用能创建一对已连接的(UNIX 族)无名 socket。在 Linux 中,完全可以把这一对 socket 当成 pipe 返回的文件描述符一样使用,唯一的区别就是这一对文件描述符中的任何一个都可读和可写。

这似乎可以是一个用来实现进程间通信管道的好方法。不过,要注意的是,为了解决我前面的提出的使用 sort 的应用问题,我们需要关闭子进程的标准输入通知子进程数据已经发送完毕,而后从子进程的标准输出中读取数据直到遇到 EOF。使用两个单向管道的话每个管道可以单独关闭,因而不存在任何问题;而在使用双向管道时,如果不关闭管道就无法通知对端数据已经发送完毕,但关闭了管道又无法从中读取结果数据。——这一问题不解决的话,使用 socketpair 的设想就变得毫无意义。

令人高兴的是,shutdown 调用 (5) 可解决此问题。毕竟 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 文档注释(6) 。我只略作几点说明:

- 本实现使用了一个链表来记录所有 dpopen 打开的文件指针和子进程 ID 的对应关系,因此,在同时用 dpopen 打开的管道的多的时候,dpclose(需要搜索链表)的速度会稍慢一点。我认为在通常使用过程中这不会产生什么问题。如果在某些特殊情况下这会是一个问题的话,可考虑更改 dpopen 的返回值类型和 dpclose 的传入参数类型(不太方便使用,但实现简单),或者使用哈希表/平衡树来代替目前使用的链表以加速查找(接口不变,但实现较复杂)。

- 当编译时在 gcc 中使用了 "-pthread" 命令行参数时,本实现会启用 POSIX 线程支持,使用互斥量保护对链表的访问。因此本实现可以安全地用于 POSIX 多线程环境之中。

- 与 popen 类似(7) ,dpopen 会在 fork 产生的子进程中关闭以前用 dpopen 打开的管道。

- 如果传给 dpclose 的参数不是以前用 dpopen 返回的非 NULL 值,当前实现除返回 -1 表示错误外,还会把 errno 设为 EBADF。对于 pclose 而言,这种情况在 POSIX 规范中被视为不确定(unspecified)行为 (8)。

- 实现中没有使用任何平台相关特性,以方便移植到其它 POSIX 平台上。

下面的代码展示了一个简单例子,将多行文本送到 sort 中,然后取回结果、显示出来:

#include <stdio.h>

#include <stdlib.h>

#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 系统中去。

本文源码下载:dpopen.zip

参考资料

1. 相应的man (3)页。在线查看:http://www.die.net/doc/linux/man/man3/popen.3.html

2. 相应的man (2)页。在线查看:http://www.die.net/doc/linux/man/man2/pipe.2.html

3. 相应的man (2)页。在线查看:http://www.die.net/doc/linux/man/man2/socketpair.2.html

4. POSIX规范:http://www.opengroup.org/onlinepubs/009695399/functions/socketpair.html

5. 相应的man (2)页。在线查看:http://www.die.net/doc/linux/man/man2/shutdown.2.html

6. Doxygen主页:http://www.stack.nl/~dimitri/doxygen/

7. POSIX规范:http://www.opengroup.org/onlinepubs/009695399/functions/popen.html

8. POSIX规范:http://www.opengroup.org/onlinepubs/009695399/functions/pclose.html

关于作者

吴咏炜,目前在 Linux 上从事高性能入侵检测系统的研发。对于开发跨平台、高性能、可重用的 C++ 代码有着浓厚的兴趣。adah@sh163.net 可以跟他联系。

全文出自 : IBM developerWorks 中国

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有