浪曦网-国内顶级 IT 视频网络编程教育平台 Www.LangSin.Com
浪曦网-国内顶级 IT 视频网络编程教育平台 Www.LangSin.Com
Linux 上实现双向进程间通信管道
该文档由国内顶级 IT 视频网络编程教育平台整理发布
本文阐述了一个使用 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); // 复制父进程的读管道到子进程的
标准
excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载
输出
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函数返回的一对文件描述符只能
浪曦网-国内顶级 IT 视频网络编程教育平台 Www.LangSin.Com
浪曦网-国内顶级 IT 视频网络编程教育平台 Www.LangSin.Com
从第一个中读、第二个中写(至少对于 Linux 是如此)。为了同时读写,就只能采取这么累赘
的两个 pipe 调用、两个文件描述符的形式了。
回页首
一个更好的方案
使用 pipe就只能如此了。不过,Linux 实现了一个源自 BSD 的 socketpair调用
(3)
,可以实现上述在同一个文件描述符中进行读写的功能(该调用目前也是 POSIX 规范的一部分
(4)
)。该系统调用能创建一对已连接的(UNIX 族)无名 socket。在 Linux 中,完全可以把这一对
socket当成 pipe 返回的文件描述符一样使用,唯一的区别就是这一对文件描述符中的任何一个
都可读和可写。
这似乎可以是一个用来实现进程间通信管道的好方法。不过,要注意的是,为了解决我前面的
提出的使用 sort 的应用问题,我们需要关闭子进程的标准输入
通知
关于发布提成方案的通知关于xx通知关于成立公司筹建组的通知关于红头文件的使用公开通知关于计发全勤奖的通知
子进程数据已经发送完毕,
而后从子进程的标准输出中读取数据直到遇到 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 系列函数等待子进程退出并取得退出代码 */
}
很清楚,这比使用两个单向管道的方案要简洁不少。我将在此基础上作进一步的封装和改进。
回页首
浪曦网-国内顶级 IT 视频网络编程教育平台 Www.LangSin.Com
浪曦网-国内顶级 IT 视频网络编程教育平台 Www.LangSin.Com
封装和实现
直接使用上面的方法,无论怎么看,至少也是丑陋和不方便的。程序的维护者想看到的是程序
的逻辑,而不是完成一件任务的各种各样的繁琐细节。我们需要一个好的封装。
封装可以使用 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
#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");
浪曦网-国内顶级 IT 视频网络编程教育平台 Www.LangSin.Com
浪曦网-国内顶级 IT 视频网络编程教育平台 Www.LangSin.Com
fprintf(fp, "pear\n");
if (dphalfclose(fp)
输出结果为:
apple
orange
pear
回页首
总结
本文阐述了一个使用 socketpair系统调用在 Linux 上实现双向进程通讯管道的方法,并提供了一
个实现。该实现提供的接口与 POSIX 规范中的 popen/pclose函数较为接近,因而非常易于使用。
该实现没有使用平台相关的特性,因而可以不加修改或只进行少量修改即可移植到支持
socketpair调用的 POSIX 系统中去。