一、背景
初次接觸服務器與客戶端的知識,先從最簡單的做起:瞭解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
#include
#include
#include
#include
#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進行數據的傳遞的。
閱讀更多 有理想的代碼dog 的文章