过滤程序:UNIX系统过滤程序从标准输入读取数据,对其进行适当处理后写到标准输出。几个过滤程序通常在shell管道命令行中线性的连接。当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出时,则该过滤程序就成为协同进程。
Korn Shell提供了协同进程,但是Bournce-again shell 和C Shell并没有提供按协同进程方式将进程连接起来的方法。协同进程在shell的后台运行,其标准输入和标准输出通过管道连接到另外一个程序。虽然初始化一个协同进程并将其输入和输出连接到另一个进程,用到的shell愈大是十分奇特的,但是协同进程在C语言中也非常有用。
注意:popen只提供连接到另一个进程的标准输入或者标准输出的一个的单向管道,而对于协同进程,则它有连接到另外一个进程的两个单向管道————一个接到其标准输出、一个连接到其标准输入,经过其处理后,再从标准输出读取数据。所以,利用popen并不能实现协同进程。
CODE:
<code>
/***********************************************************************************/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h> #define MAX_LINE 2048
static void sig_pipe(int);
int main(void)
{
int n, fd1[2], fd2[2];
char line[MAX_LINE];
pid_t pid;
if(signal(SIGPIPE, sig_pipe) == SIG_ERR)
{
printf("init signal error: %s\n", strerror(errno));
exit(1);
}
if((pipe(fd1) < 0) || (pipe(fd2) < 0))
{
printf("create pipe error: %s\n", strerror(errno));
exit(1);
}
if ((pid = fork()) < 0)
{
printf("fork error: %s\n", strerror(errno));
exit(1);
}
else if (pid > 0)
{
close(fd1[0]);
close(fd2[1]);
while (fgets(line, MAX_LINE, stdin) != NULL)
{
n = strlen(line);
if (write(fd1[1], line, n) != n)
{
printf("write to pipe failed: %s\n", strerror(errno));
exit(1);
}
if ((n = read(fd2[0], line, MAX_LINE)) < 0)
{
printf("read from pipe failed: %s\n", strerror(errno));
exit(1);
}
if (n == 0)
{
printf("child closed pipe\n");
break;
}
line[n] = 0;
if (fputs(line, stdout) == EOF)
{
printf("fputs error: %s\n", strerror(errno));
exit(1);
}
}
if (ferror(stdin))
{
printf("fgets error: %s\n", strerror(errno));
exit(1);
}
}
else
{
close(fd1[1]);
close(fd2[0]);
if (fd1[0] != STDIN_FILENO)
{
if (dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)
{
printf("dup2 error: %s\n", strerror(errno));
exit(1);
}
close(fd1[0]);
}
if (fd2[1] != STDOUT_FILENO)
{
if (dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
{
printf("dup2 error: %s\n", strerror(errno));
exit(1);
}
close(fd2[1]);
}
if (execl("./add2", "add2", (char *)0) < 0)
{
printf("execl error: %s\n", strerror(errno));
exit(1);
}
}
exit(0);
} static void
sig_pipe(int signo)
{
printf("SIG_PIPE caught\n");
exit(1);
}
/***********************************************************************************/
</code> 使用opopen:
<code>
/***********************************************************************************/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h> #define MAX_LINE 2048
static void sig_pipe(int);
int
main(void)
{
int n, fd[2];
char line[MAX_LINE];
pid_t pid;
FILE *fpin, *fpout;
if( signal(SIGPIPE, sig_pipe) == SIG_ERR )
{
printf("init signal error: %s\n", strerror(errno));
exit(1);
}
if((fpout = popen("./add2", "w")) == NULL)
{
printf("popen error: %s\n", strerror(errno));
exit(1);
}
if((fpin = popen("./add2", "r")) == NULL)
{
printf("popen error: %s\n", strerror(errno));
exit(1);
}
while(fgets(line, MAX_LINE, stdin) != NULL)
{
n = strlen(line);
fd[0] = fileno(fpout);
if (write(fd[0], line, n) != n)
{
printf("write to pipe failed: %s\n", strerror(errno));
exit(1);
}
fd[1] = fileno(fpin);
if ((n = read(fd[1], line, MAX_LINE)) < 0)
{
printf("read from pipe failed: %s\n", strerror(errno));
exit(1);
}
else if (n == 0)
{
printf("child closed pipe\n");
break;
}
else
{
line[n] = 0;
if (fputs(line, stdout) == EOF)
{
printf("fputs error: %s\n", strerror(errno));
exit(1);
}
}
}
if(ferror(stdin))
{
printf("fgets error: %s\n", strerror(errno));
exit(1);
}
exit(0);
} static void
sig_pipe( int signo )
{
printf("SIG_PIPE caught\n");
exit(1);
}
/***********************************************************************************/
</code>
过滤程序:
<code>
/***********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h> #define MAX_LINE 2048
int main(void)
{
int n, int1, int2;
char line[MAX_LINE];
while ((n = read(STDIN_FILENO, line, MAX_LINE)) > 0)
{
if (sscanf(line, "%d%d", &int1, &int2) == 2)
{
sprintf(line, "%d\n", int1 + int2);
n = strlen(line);
if (write(STDOUT_FILENO, line, n) != n) {
printf("write error: %s", strerror(errno));
exit(1);
}
}
else
{
if(write(STDOUT_FILENO, "Invalid args\n", 13) != 13)
{
printf("write error: %s", strerror(errno));
exit(1);
}
}
}
exit(0);
}
/***********************************************************************************/
</code>
他们的实现原理是完全不一样的。使用popen时,popen其实是实现了以下三个动作:1)创建一个pipe; 2)fork一个子进程,使得子进程运行新的程序;3)将子进程的标准输出或者标准输入重定位到父进程。即,父进程通过管道向子进程写数据或者通过管道从子进程读取数据。而这里的“读”或者“写”是根据调用popen时的第二个参数决定的。
如果使用两次popen,如上面的程序所示,那么实际上父进程是在与两个子进程通过管道通信,而不是一个!
而对于协同进程而言,它是父进程通过一个管道从子进程读取数据(子进程的标准输出输出到此管道),同时父进程通过子进程向子进程写数据(子进程的标准输入来自此管道),这里的两个子进程其实是一个进程。
所以,他们有着本质的区别!