你可以用system, popen,pipe 实现两个进程的通讯!
//下面是uClibc-0.9.33 的实现,为方便阅读,代码有删减。
//由以下代码可知, system 就是调用 fork() 函数,
//子进程调用execl 执行command命令;
//主进程等待子进程完成。
int __libc_system(const char *command)
{
//此简化代码忽略了对信号的处理代码,那部分功能主要实现主进程忽略SIGQUIT, SIGINT ...
int wait_val, pid;
if (command == 0)
return 1;
if ((pid = vfork()) < 0) return -1;
if (pid == 0) { // 子进程执行程序 execl,
execl("/bin/sh", "sh", "-c", command, (char *) 0);
_exit(127);
}
//主进程等待子进程结束
if (wait4(pid, &wait_val, 0, 0) == -1) return -1
return wait_val;
}
优点: system() 调用使用简单,
缺点:当我们需要与程序交互时,system 就不能胜任了。
system() 函数调用相当于批命令调用或者执行程序命令
popen 函数可以按读的方式或者写的方式打开管道,通过管道与command 程序进行通讯。
代码有删减, 英文是原注释/**/,含中文行为本人注释//。
由以下代码可知,popen 就是创建一个pipe, 父进程打开pipe的一端,子进程打开另一端。
子进程把pipe 端与stdin 或 stdout 关联,执行 execl(command) 命令, 这样通过pipe完成
了父子进程的通讯。
它就等价于linux-shell 中的重定向> 或者<
FILE *popen(const char *command, const char *modes)
{
FILE *fp;
int pipe_fd[2]; //pipe_fd[0]为读端,pipe_fd[1]为写端
int parent_fd;
int child_fd;
int zeroOrOne;
pid_t pid;
zeroOrOne = 0; // Assume write mode.
if (modes[0] != 'w') { // if not write mode, it must be read mode
++zeroOrOne;
if (modes[0] != 'r') { /* Oops! Parent not reading either! */
__set_errno(EINVAL); // 打开模式只能是读或者写, 否则出错。
goto RET_NULL; // 写模式,zeroOrOne = 0; 读模式 zeroOrOne = 1;
}
}
if (pipe(pipe_fd)) {// 创建pipe,得到两个描述符 总是fd[0]为输入端, fd[1]为输出端
goto FREE_PI;
}
child_fd = pipe_fd[zeroOrOne]; //使得child_fd, parent_fd 为管道的两端
parent_fd = pipe_fd[1-zeroOrOne]; //写模式(父写子读),pipe_fd[0]->child_fd, pipe_fd[1]->parent_fd
//读模式(父读子写),pipe_fd[0]->parent_fd,pipe_fd[1]->child_fd
if (!(fp = fdopen(parent_fd, modes))) { // 父进程读或写管道。返回文件流指针。
close(parent_fd);
close(child_fd);
goto FREE_PI;
}
if ((pid = vfork()) == 0) { /* Child of vfork... */
close(parent_fd);
if (child_fd != zeroOrOne) {
dup2(child_fd, zeroOrOne);// 子进程会复制描述符到zeroOrOne,
close(child_fd);// 实际上是将管道端关联重定向到stdin(主进程写模式) 或关联重定向到stdout(主进程读模式)
}
execl("/bin/sh", "sh", "-c", command, (char *)0);
_exit(127);
}
if (pid > 0) { /* Parent of vfork... */
return fp;
}
/* If we get here, vfork failed. */
fclose(fp); /* Will close parent_fd. */
FREE_PI:
free(pi);
RET_NULL:
return NULL;
}
补充: dup2(child_fd, 0); 就是把child_fd 复制到stdin上,这样子进程从stdin读取,实际上是读取的child_fd, 就是所谓的输入重定向.
同理:dup2(child_fd, 1); 就是把child_fd 复制到stdout上,这样子进程向stdout输出信息,实际上是向child_fd输出信息.
哈哈哈!!! 使用了偷梁换柱之法. 由此骗过了子进程. 子进程以为向stdout打印,实际上打到了管道的一端, 另一端连接的是父进程的读端,被父进程读走了. 同理,父进程打管道的一端搭在了子进程的stdin端, 父进程向管道写东西,子进程以为是从stdin读进来的呢! 很有趣! 是吗? 这是欺骗的手段,或者沟通的桥梁!
//本例演示了popen,pclose的使用
#include
int main()
{
FILE * fp;
char buf[20] = {0};
fp = popen("ls","r");
if(NULL == fp)
{
perror("popen error!\n");
return -1;
}
while(fgets(buf, 20, fp) != NULL)
{
printf("%s", buf);
}
pclose(fp);
return 0;
}
popen按读方式打开”ls”程序,已经将ls 输出重定向到管道输入端,我们的fp 是管道输出端, 所以程序运行达到了目的.
//执行一个shell命令,输出结果逐行存储在vecStr中,并返回行数
int readPipe(const char *cmd, vector<string> &vecStr) {
vecStr.clear();
FILE *pp = popen(cmd, "r"); //建立管道
if (!pp) {
return -1;
}
char buf[1024]; //设置一个合适的长度,以存储每一行输出
while (fgets(buf, sizeof(buf), pp) != NULL) {
if (buf[strlen(buf) - 1] == '\n') {
buf[strlen(buf) - 1] = '\0'; //去除换行符
}
vecStr.push_back(buf);
}
pclose(pp); //关闭管道
return vecStr.size();
}
//但是,当我们即想向pipe 写, 又想从pipe 读, 现成的popen 就不能胜任了,
//popen 只能创建一条管道,或者是读管道,或者是写管道。
//要想同时与程序实现读,写操作(双向交互), 需要我们自己书写进程代码.
//是的,通过两个管道,一个读管道,一个写管道。下面给一个范例.
这个范例演示了我们的父进程与子进程通讯, 并打印了与子进程的通讯内容.
子进程并不知道与它通讯的到底是人通过键盘跟它下命令,还是程序在跟它下命令,它的描述符已经被接到管道上了.
#include
#include
#include
#include
#include
#include
#define READ 0
#define WRITE 1
// 注,回车有非常重要的作用,否则对端会阻塞在读上而不能继续
// printf 没有"\n"也不会显示打印,除非用fflush或程序退出。不信可以试试。
// 这些都是缓冲若得祸!
int doubleInteract(const char* cmdstring)
{
const char *sendStr = "hello,child\n";
int pipeR[2]; // 父读子写
int pipeW[2]; // 父写子读
pid_t pid;
char buf[1024];
int len=sizeof(buf);
memset(buf, 0, len);
/*初始化管道*/
pipe(pipeR);
pipe(pipeW);
if ((pid = fork()) < 0) return -1;
if (pid > 0) /* parent process */
{
close(pipeW[READ]);
close(pipeR[WRITE]);
read(pipeR[READ], buf, len); //由于这个len足够大,读不到回车又不够len长度不返回。
// 读到了东西,需要分析内容,做出正确回应... , 更好的做法是启动一个线程,专门接受管道输入
// 这里只简单回应"hello..."
printf("child:%s", buf); // 这里会写到屏幕上
write(pipeW[WRITE], sendStr, strlen(sendStr)+1); // 这里把"..." 发到管道上
printf("parent:%s", sendStr);
#if 1
memset(buf,0,len);
read(pipeR[READ], buf, len); //再读child 响应
printf("child:%s", buf); // 这里会写到屏幕上
#endif
close(pipeW[WRITE]);
close(pipeR[READ]);
waitpid(pid, NULL, 0);
}
else /* child process, 关闭写管道的写端,读管道的读端,与父进程正好相对 */
{
close(pipeW[WRITE]);
close(pipeR[READ]);
//重定向子进程的标准输入,标准输出到管道端
if (pipeW[READ] != STDOUT_FILENO)
{
if (dup2(pipeW[READ], STDIN_FILENO) != STDIN_FILENO)
{
return -1;
}
close(pipeW[READ]);
}
if (pipeR[WRITE] != STDOUT_FILENO)
{
if (dup2(pipeR[WRITE], STDOUT_FILENO) != STDOUT_FILENO)
{
return -1;
}
close(pipeR[WRITE]);
}
execl("/bin/sh", "sh", "-c", cmdstring, (char*)0);
exit(127);
}
return 0;
}
int main()
{
doubleInteract("./myShell");
return 0;
}
//这里给一个myShell脚本范例,从键盘输入,向屏幕输出.如下:
#!/bin/bash
echo "hello! say Something"
read
echo "you say:$REPLY,bye bye!"
该例没有采用把管道端向输入或输出重定向的技术,
而是直接用pipe通讯, 父子进程协作,将数值从0加到10;
规则是从管道中拿到数,加1,再把数推出去。
$ cat main.cpp
#include
#include
#include
#include
#include
#define READ 0
#define WRITE 1
int main(void)
{
int x;
pid_t pid;
int pipe1[2],pipe2[2];
/*初始化管道*/
pipe(pipe1);
pipe(pipe2);
pid = fork();
if(pid < 0)
{
printf("create process error!/n");
exit(1);
}
if(pid == 0) //子进程
{
close(pipe1[WRITE]);
close(pipe2[READ]);
do
{
read(pipe1[READ],&x,sizeof(int));
printf("child %d read: %d\n",getpid(),x++);
write(pipe2[WRITE],&x,sizeof(int));
}while(x<=9);
//读写完成后,关闭管道
close(pipe1[READ]);
close(pipe2[WRITE]);
exit(0);
}
else if(pid > 0) //父进程
{
close(pipe2[WRITE]);
close(pipe1[READ]);
x = 1;
//每次循环向管道1 的1 端写入变量X 的值,并从
//管道2 的0 端读一整数写入X 再对X 加1,直到X 大于10
do{
write(pipe1[WRITE],&x,sizeof(int));
read(pipe2[READ],&x,sizeof(int));
printf("parent %d read: %d\n",getpid(),x++);
}while(x<=9);
//读写完成后,关闭管道
close(pipe1[WRITE]);
close(pipe2[READ]);
waitpid(pid,NULL,0);
exit(0);
}
}
范例均经过测试,可直接使用。