一、背景
初次接触服务器与客户端的知识,先从最简单的做起:了解linux中使用的计算器。linux中使用的计算器是最为简单的服务器与客户端协同工作的例子。
二、从管道到服务器与客户端
之前详细的解释过管道的工作原理,但传统的Unix管道只是单方向的传送数据。打个比方,在linux命令行下使用cat /etc/passwd | grep Jack命令。这个命令将cat的输出结果通过管道传送到grep命令的标准输入中。但这只是一个单向的数据传送,grep的输出结果并不会进行回传。而若有两个进程A、B,他们两个协同进行工作,A进程将其输出结果传送给B进程,B进程处理数据完毕后再返回给A进程。这样,它们之间并不是单向的管道,而是一个双向的管道。B对A提供服务,而A是B的客户。A就叫做客户端,B叫做服务器。
三、bc与dc
打开我们的linux命令行终端,输入bc命令调用linux的计算器,写入2+2。使用enter键则会返回结果。
这时候大家可能会觉得我们向bc进程输入了数据,bc进行运算后将结果返回给了屏幕终端。而实际上并非如此。
实际上bc只是一个用户的界面,让用户输入数值与给用户返回结果而已。这个可以使用man bc命令来查看。
上面简介说bc是一个计算器语言。而看以下dc的手册。
这里可以看到,dc是一个计算器。什么意思呢?
其实是说,这里的bc只是一个界面,而dc才是真正数据进行计算的地方。bc将接收到的数据传送给dc进行运算,运算结束后,dc将结果返回给bc显示输出。
那么,也就是说,bc其实一个客户端,而dc是一个服务器。
四、编写bc
搞清楚了bc与dc之间的关系,那么,我们就可以使用现有的知识编写出自己的bc。当然,这里的bc指的是用户的交互界面。而后台的计算还是要交给dc。
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<unistd.h>
#define oops(m,x) {perror(m);exit(x);}
void be_bc(int *,int *);
void be_dc(int *,int *);
void fatal(char *);
int main()
{
int pid,todc[2],fromdc[2];
if(pipe(todc) == -1 || pipe(fromdc) == -1)
oops("pipe failed",1);
if((pid = fork()) == -1)
oops("fork failed",2);
if(pid == 0)
be_dc(todc,fromdc);
else{
be_bc(todc,fromdc);
wait(NULL);
}
}
void be_bc(int todc[2],int fromdc[2])
{
int num1,num2;
char operation[BUFSIZ],message[BUFSIZ],*fgets();
FILE *fpout,*fpin,*fdopen();
close(todc[0]);
close(fromdc[1]);
fpout = fdopen(todc[1],"w");
fpin = fdopen(fromdc[0],"r");
if(fpout == NULL || fpin == NULL)
fatal("Error convering pipes to streams");
while(printf("tinybc:"),fgets(message,BUFSIZ,stdin) != NULL){
if(sscanf(message,"%d%[-+*/^]%d",&num1,operation,&num2) != 3){
printf("syntax error\n");
continue;
}
if(fprintf(fpout,"%d\n%d\n%c\np\n",num1,num2,*operation) == EOF)
fatal("Error writing");
fflush(fpout);
if(fgets(message,BUFSIZ,fpin) == NULL)
break;
printf("%d %c %d = %s",num1,*operation,num2,message);
}
fclose(fpout);
fclose(fpin);
}
void be_dc(int in[2],int out[2])
{
if(dup2(in[0],0) == -1)
oops("dc:cannot redirect stdin",3);
if(dup2(out[1],1) == -1)
oops("dc:cannot redirect stdout",4);
close(in[1]);close(out[0]);
close(in[0]);close(out[1]);
execlp("dc","dc",NULL);
oops("cannot run dc",5);
}
void fatal(char *mess)
{
fprintf(stderr,"fatal:%s\n",mess);
exit(1);
}
此程序的实现原理很简单,只是使用了管道方面的系统调用然后进行重定向。将父程序的输出输入到dc中,将dc的输出输入到父进程中就行了。运行结果如下:
在这里,我们一定要清楚哪些数据是dc通过管道传递给bc的。
五、总结
在这里简单的阐述了一下何为服务器与客户端。并且通过一个实际的例子:linux中的计算器来具体的了解了一下协同进程的工作方式。最后编写了一段bc交互程序代码来了解bc是如何与dc进行数据的传递的。