我们将程序分为两个部分
1.主程序bcmain.c
#include "headall.h"
/*
* 实现bc程序:其主要功能是用来进行加减乘除的运算
* 其本质是一个客户端/服务器的模型
*
* +---------+ pipe->dc +----------+
* stdin 0 >============> |
* | bc | | dc |
* stdout1 <============< |
* +---------+ pipe<-dc +----------+
*
* 用户首先会连接到bc并将bc连接到dc,调用dc程序后返回结果
* 该程序包括四个主要方面
* 1. bc与dc之间通过管道通信:由于管道是单向的,因此要建立两个管道。
* 2. 创建一个新的进程来运行dc
* 3. 在新进程中,由于我们要execlp shell的dc程序,dc程序的输入是标准输入,
* dc程序的输出是标准输出,而我们希望重定向这两个到管道
*
* 4. 在父进程中,我们会读取用户的输入,进行解析,将命令通过管道传给dc
* dc进行读取数据,返回结果给bc,bc显示给用户
*
* 3 4 5 6
* | | | |
* | | | |
* | | | |
* +———+ +--+
*
*/
int main(){
int pid;
// two pipe
int todc[2],fromdc[2];
// make two pipes
if( pipe(todc) == -1 || pipe(fromdc) == -1)
oops("pipe failed",1);
//printf("todc = %d,%d\n",todc[0],todc[1]);
//printf("todc = %d,%d\n",fromdc[0],fromdc[1]);
pid = fork();
switch(pid){
case -1: oops("fork failed",1);break;
// in child process
case 0:
dc_deal(todc,fromdc);
break;
// parent process
default:
bc_deal(todc,fromdc);
}
}
2.处理程序proclib.c
#include "headall.h"
int dc_deal(int *,int *);
int bc_deal(int *,int *);
/*
* review 匿名管道
* pipe(array[2]);
* 创建一个管道,并为这个数组的两个元素创建文件描述符
* 这里要注意:
* array[0] 是从管道中读取数据
* array[1] 是向管道中写数据
*
*/
//父进程的处理函数
int bc_deal(int *todc,int *fromdc){
char message[BUFSIZ],operation[BUFSIZ];
int num1,num2;
// 管道1,写入dc的管道
// 父子进程共享管道
// 在父进程方面关闭 todc[0],关闭读管道
close(todc[0]);
// 管道2:读取dc的管道
// 在父进程方面关闭 fromdc[1],关闭写管道
close(fromdc[1]);
FILE *fpout,*fpin;
// 转换文件描述符为文件流
fpout = fdopen(todc[1],"w");
fpin = fdopen(fromdc[0],"r");
if(fpout == NULL || fpin == NULL)
oops("error to convert the pipes to streams",1);
while(printf("our bc:"),fgets(message,BUFSIZ,stdin)!=NULL){
// 这里有兴趣的可以查一下sscanf的用途
// 此函数很强大
// %[- +]表示操作符号,这里一定要注意
// -号不能放在中间,否则会被当做 [A-Z]中的符号而无效的
// 如果放中间会让减法没有用。
if(sscanf(message,"%d%[- + * / ^]%d",
&num1,operation,&num2)!=3){
//printf("op = %c\n",operation);
//printf("num1 = %d,num2 = %d\n",num1,num2);
printf("syntax error\n");
continue;
}
//printf("op = %c\n",operation);
//printf("num1 = %d,num2 = %d\n",num1,num2);
if(fprintf(fpout,"%d\n %d\n %c\np\n",num1,num2,
*operation)==EOF)
oops("error writing",1);
fflush(fpout);
if(fgets(message,BUFSIZ,fpin)==NULL)
break;
printf("%d %c %d = %s",num1,*operation,num2,message);
}
fclose(fpout);
fclose(fpin);
}
// 子进程的处理函数
int dc_deal(int todc[2],int fromdc[2]){
// printf("todc[2]= %d,%d\n",todc[0],todc[1]);
// printf("fromdc[2]= %d,%d\n",fromdc[0],fromdc[1]);
// 管道1,写入dc的管道
// 父子进程共享管道
// 在子进程方面关闭 todc[1],关闭写管道
close(todc[1]);
// 管道2:读取dc的管道
// 在子进程方面关闭 fromdc[0],关闭读管道
close(fromdc[0]);
// printf("todc[2]= %d,%d\n",todc[0],todc[1]);
// printf("fromdc[2]= %d,%d\n",fromdc[0],fromdc[1]);
// 子进程方面此时拥有的是:
// 与管道1相关的是,读管道的 todc[0]
// 与管道2相关的是,写管道的 fromdc[1]
// 我们需要在该子进程中执行dc,
// dc程序的数据来源是标准输入,结果会输出到标准输出
// 因此我们要对该程序进行重定向
// 读管道 todc[0]作为数据来源定向到标准输入
if(dup2(todc[0],0)==-1)
oops("dup2 failed",1);
// todc[0]完成使命
close(todc[0]);
//printf("after dup2 0,0\n");
//printf("todc[2]= %d,%d\n",todc[0],todc[1]);
//printf("fromdc[2]= %d,%d\n",fromdc[0],fromdc[1]);
// 写管道 fromdc[1]作为数据的输出定向到标准输出
if(dup2(fromdc[1],1) == -1)
oops("dup2 failed",1);
// fromdc[1]关闭即可
close(fromdc[1]);
//printf("after dup2 1,1\n");
//printf("todc[2]= %d,%d\n",todc[0],todc[1]);
//printf("fromdc[2]= %d,%d\n",fromdc[0],fromdc[1]);
// 我们可以干正事了
execlp("dc","dc",NULL);
oops("dc failed",5);
}