【Unix/Linuxプログラミング実践】パイプで双方向通信を実現する-bc計算機を例に
前言
ほとんどのバージョンのUnixにはbc計算機が含まれています.
実際、ほとんどのバージョンのbcは入力のみを分析し、内部でdc計算機プログラムを起動し、パイプを通じて通信します.dcはスタックベースの計算機で、逆ポーランド式を受信し、演算を実行した後、結果を標準出力に送る.
bcはdc標準出力に接続されたパイプから結果を読み出し,ユーザに転送する.
実際、bcはユーザーインタフェースを提供し、dcが提供するサービスを使用しています.これは、単純なクライアント/サーバモデルです.
bc/dcペアは協同プロセス(coroutines)と呼ばれる.
bcの構想を書く
1.bcは2つのパイプを作成します(pipeを使用します).
2.bcは新しいプロセスを作成します(dcを実行し、forkを使用します).
3.新しいプロセスが作成された後、exec dcを実行する前に、その標準入力と標準出力をパイプにリダイレクトします.
4.exec dcを実行する.
5.親プロセスbcでは、ユーザの入力を読み取り分析し、コマンドをパイプを介してdc、dcを介して応答を読み取り、応答をパイプを介してユーザに渡す.
コード実装と分析
締めくくり
このコードは『Unix/Linuxプログラミング実践チュートリアル』第11章から参照してください.
自分で真似して叩いてみると印象が深まり、一例としてpipe、fork、dup、execなどの知識が溶け込んでいます.
注意すべき点の1つは、fdopenを使用してファイル記述子を開き、fprintfとfgetsを使用してパイプとdcを介して通信できるようにすることです.
ほとんどのバージョンのUnixにはbc計算機が含まれています.
実際、ほとんどのバージョンのbcは入力のみを分析し、内部でdc計算機プログラムを起動し、パイプを通じて通信します.dcはスタックベースの計算機で、逆ポーランド式を受信し、演算を実行した後、結果を標準出力に送る.
bcはdc標準出力に接続されたパイプから結果を読み出し,ユーザに転送する.
実際、bcはユーザーインタフェースを提供し、dcが提供するサービスを使用しています.これは、単純なクライアント/サーバモデルです.
bc/dcペアは協同プロセス(coroutines)と呼ばれる.
bcの構想を書く
1.bcは2つのパイプを作成します(pipeを使用します).
2.bcは新しいプロセスを作成します(dcを実行し、forkを使用します).
3.新しいプロセスが作成された後、exec dcを実行する前に、その標準入力と標準出力をパイプにリダイレクトします.
4.exec dcを実行する.
5.親プロセスbcでは、ユーザの入力を読み取り分析し、コマンドをパイプを介してdc、dcを介して応答を読み取り、応答をパイプを介してユーザに渡す.
コード実装と分析
/** tinybc.c * a tiny calculator that uses dc to do its work ** * demonstrates bidirectional pipes ** * input looks like number op number which ** tinybc converts into number
number
op
p ** and passes result back to stdout ** ** +-----------+ +----------+ ** stdin >0 >== pipetodc ====> | ** | tinybc | | dc - | ** stdout <1 <== pipefromdc ==< | ** +-----------+ +----------+ ** ** * program outline ** a. get two pipes ** b. fork (get another process) ** c. in the dc-to-be process, ** connect stdin and out to pipes ** then execl dc ** d. in the tinybc-process, no plumbing to do ** just talk to human via normal i/o ** and send stuff via pipe ** e. then close pipe and dc dies ** * note: does not handle multiline answers **/
#include <stdio.h>
#define oops(m, x) { perror(m); exit(x); }
main()
{
int pid, todc[2], fromdc[2];
/* */
if ( pipe(todc) == -1 || pipe(fromdc) == -1 )
oops("pipe failed", 1);
/*fork , */
if ( (pid = fork()) == -1 )
oops("cannot fork", 2);
if ( pid == 0 ) /* , dc*/
be_dc(todc, fromdc);
else /* , bc*/
{
be_bc(todc, fromdc);
wait(NULL); /* */
}
}
be_dc(int in[2], int out[2])
{
/* in[0]( ) 0( stdin) */
if ( dup2(in[0], 0) == -1 )
oops("dc:cannot redirect stdin",3);
close(in[0]); /* , fd 0*/
close(in[1]); /* */
/* , */
if ( dup2(out[1], 1) == -1 )
oops("dc:cannot redirect stdout",4);
close(out[1]);
close(out[0]);
/* now execl dc with the - option */
execlp("dc", "dc", "-", NULL);
oops("Cannot run dc",5);
}
be_bc(int todc[2], int fromdc[2])
{
int num1, num2;
char op[BUFSIZ], message[BUFSIZ], *fgets();
FILE *fpout, *fpin, *fdopen();
/*setup*/
close(todc[0]); /* */
close(fromdc[1]); /* */
/* FILE * , I/O */
fpout = fdopen(todc[1], "w");
fpin = fdopen(fromdc[0], "r");
if ( fpout == NULL || fpin == NULL)
fatal("Error convering pipes to streams");
/*main loop*/
/* */
while ( printf("tinybc: "), fgets(message, BUFSIZ, stdin) != NULL)
{
/* message */
if (sscanf(message, "%d%[-+*/^]%d", &num1, op, &num2) != 3)
{
printf("syntax error
");
continue;
}
/* */
if (fprintf(fpout, "%d
%d
%c
p
", num1, num2, *op) == EOF)
fatal("Error writing");
fflush(fpout);
/* */
if (fgets(message, BUFSIZ, fpin) == NULL)
break;
printf("%d %c %d = %s", num1, *op, num2, message);
}
fclose(fpout); /*close pipe*/
fclose(fpin); /*dc will see EOF*/
}
fatal(char mess[])
{
fprintf(stderr, "Error: %s
", mess);
exit(1);
}
締めくくり
このコードは『Unix/Linuxプログラミング実践チュートリアル』第11章から参照してください.
自分で真似して叩いてみると印象が深まり、一例としてpipe、fork、dup、execなどの知識が溶け込んでいます.
注意すべき点の1つは、fdopenを使用してファイル記述子を開き、fprintfとfgetsを使用してパイプとdcを介して通信できるようにすることです.