【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を介して応答を読み取り、応答をパイプを介してユーザに渡す.
コード実装と分析
/** 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を介して通信できるようにすることです.