在做远程通讯程序的时候经常有这样一类用例:
client端发送一个请求,需要从server获取相关信息,比如client端需要知道server根目录下目录结构等等,这样怎么做呢?
首先,在这里忽略网络通讯的相关细节,就关注于服务器端怎么去得到目录结构。也许你会利用opendir, readdir再加上if else, while, for 之类的字符码了一堆功能貌似强大的代码,当然这完全能满足你的要求!但是有什么更好的方法呢?试想如果你需要执行非常复杂的一套命令怎么办?比如在client端时刻监控到server端的网络连接状态,难道非得利用socket来发送heartbeat消息来监控么?那样代价就太大了。
也许这是你会想到我们有ls, ping命令,对了,linux中这些实用的命令,还有shell脚本提供了强大的功能,如果我们能在server端执行这些脚本并且得到他们的输出就太好了!是的,执行一个命令太简单了,exec函数族就可以解决问题,但是关键问题是如何获得命令的输出呢?
好,说到输出,这些命令的输出都是写在进程标准输出上文件描述符上。那就好办了,我们让命令输出到自定义的文件描述符上,然后去读取这个文件描述符!而这里涉及到父子进程的通讯,我们利用管道就可以实现了。现在整理一下顺序:
1,父进程创建一个pipe
2,fork子进程
3,父进程读取pipe,等待子进程写入数据
4,子进程中重定向stdout为pipe的写端
5,子进程执行命令
6,子进程执行完毕,推出
7,父进程读取pipe完毕,退出
到这一步我们就获得了一个功能强大的命令执行器,它可以执行任何程序以及shell脚本,然后对返回的字符串分析重构得到我们想要的格式,大功告成!这样解决“在client端时刻监控到server端的网络连接状态“的问题就易如反掌了,只需要执行ping -t然后分析返回的字符串,就可以了!就这么简单。
但是,有更好的解决方案吗? 当然有!请看popen函数
#include <stdio.h>
FILE* popen(const char* cmdstring, const char type);
popen内部先执行fork,然后调用exec执行cmdstring,并且返回一个标准i/o文件指针,如果type是"r",则返回cmdstring的标准输出,如果type是“w”,则返回cmdstring的标准输入。这不正是我们上面做的工作吗? demo代码如下:
#include <stdio.h>
int main( void )
{
char psBuffer[128];
FILE *pPipe;
if( (pPipe = popen( "ls -l /", "r" )) == NULL )
return 0;
while(fgets(psBuffer, 128, pPipe))
printf(psBuffer);
pclose(pPipe);
return 0;
}
原来一切如此简单! 更难得的是popen是c标准函数库提供的,也就是说也适用于windows平台,轻松做到平台无关!
................................................................................
后记
这是我在两年前的一个项目中遇到的问题,server端需要执行各种各样复杂的命令,由于是在windows系统中,当时的方案是利用重定向管道和createprocess包装了一个command executer,工作非常稳定!但是在平台移植的时候遇到问题,最终找了popen!