1

Unixプログラミングの演習として、2つのパイプを作成し、子をフォークしてから、パイプを介して子との間でテキストを送受信するプログラムを作成しました。子プロセスで、関数のコードを使用してデータを読み書きする場合に機能しますfilter。ただし、子がパイプをstdinとstdoutにリダイレクトして(を使用して)ユーティリティdup2を実行(を使用して)しようとすると、機能せず、どこかでスタックします。このコードは関数内にあります。問題は、なぜですか?コードは次のとおりです。execlptrfilter2

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>

void err_sys(const char* x) { perror(x); exit(1); } 

void upper(char *s) { while((*s = toupper(*s))) ++s; }

void filter(int input, int output)
{   
    char buff[1024];
    bzero(buff, sizeof(buff));
    size_t n = read(input, buff, sizeof(buff));

    printf("process %ld: got '%s'\n", (long) getpid(), buff);

    upper(buff);
    write(output, buff, strlen(buff));
}   

void filter2(int input, int output)
{   
    if (dup2(input, 0) != 0) err_sys("dup2(input, 0)");
    if (dup2(output, 1) != 1) err_sys("dup2(output, 1)");
    execlp("/usr/bin/tr", "tr", "[a-z]", "[A-Z]" , (char*)0);
}   

int main(int argc, char** argv) 
{   
    int pipe1[2];
    int pipe2[2];
    if (pipe(pipe1) < 0) err_sys("pipe1");
    if (pipe(pipe2) < 0) err_sys("pipe2");

    pid_t pid;
    if ((pid = fork()) < 0) err_sys("fork");
    else if (pid > 0)
    {   
        close(pipe1[0]);
        close(pipe2[1]);
        char* s = "Hello there, can you please uppercase this and send it back to me? Thank you!";
        write(pipe1[1], s, strlen(s));

        char buff[1024];
        bzero(buff, sizeof(buff));
        size_t n = read(pipe2[0], buff, sizeof(buff));
        pid_t mypid = getpid();
        printf("process %ld: got '%s'\n", (long) mypid, buff);
    } else
    {   // Child.
        close(pipe1[1]);
        close(pipe2[0]);

        filter(pipe1[0], pipe2[1]); 
        //filter2(pipe1[0], pipe2[1]);  // FIXME: This doesn't work
    }   
    return 0;
} 
4

4 に答える 4

2

mainの親プロセスには、小さな変更が必要です。

/* Was: */
char* s = "Hello there, can you please uppercase this and send it back to me? Thank you!";
write(pipe1[1], s, strlen(s));
/* add: */
close(pipe1[1]);

他の人はバッファリングについて言及していますが、それは実際にはバッファリングの問題ではありません。プロセス間通信についてです。

パイプが「パイプ」と呼ばれ、「コンベヤーベルト」と呼ばれないのには理由があります。パイプは、コンベヤーベルトとは異なり、パッケージの境界を保持しません。パイプは単なるバイトのストリームです。write大量のバイトをストリームにダンプしますが、そうしているという事実をマークしません。したがって、コードは同じように次のようになっている可能性があります。

    write(pipe1[1], s, strlen(s)/2);
    write(pipe1[1], s + strlen(s)/2,
                    strlen(s+strlen(s)/2));

またはwritesの他の組み合わせ。受信側は、便利なバイト数(つまり、便利なバイト数)を読み取り、それらを処理します。それはおそらく次のようなことをします:

     read(stdin, buffer, BUFSIZ);

これは、BUFSIZバイトが読み取られるか、EOFに到達するまで戻りません。読み取りプロセスのシステムコールに到達して読み取りの長さをさかのぼって変更することはできないため、読み取りプロセスに実際に作業を終了させる唯一の方法は、EOF表示を取得するように調整することです。それはパイプを閉じることです。したがって、上記の私の解決策。

2つの連続したリクエストをストリームに入れることができないため、これは必ずしも便利ではありません。2つのプロセス間の通信の確立にはかなりのオーバーヘッドが伴います(特にサーバープロセスを新たに開始する必要がある場合)。要求を「パイプライン化」する場合(すべての要求の最後に応答が送信されるようにするため)、「パッケージの境界」を明確に示す通信プロトコルを設計する必要があります。リクエスト間の分割。つまり、パイプを使用して独自のコンベヤーベルトを実装する必要があります。

通信プロトコルには、両端からのサポートが必要です。クライアントから実装するだけでは不十分です。trしたがって、任意のプロトコルを理解することはできません。それはそれがすることをするだけです(EOFに読み取り、送信を邪魔するのに十分なバイトがあると感じたときに変換されたバイトを書き込みます)。したがって、このアイデアを試してみたい場合は、クライアントプロセスとサーバープロセスの両方を作成する必要があります。

利用可能な最も単純なパッケージプロトコルは、おそらくDanielBernsteinのnetstringsです。リンクには実際のコードが含まれていますが、これは非常に単純ですが、基本的な考え方は次のとおりです。文字列は、長さを10進数、コロン(:)、長さで約束された正確なバイト数として送信することで送信されます。ライターは、送信する前に送信するバイト数を知る必要があります。読者は「:」まで読む必要があります(djbはscanfこれを行うために使用します。これは、しばしば過小評価されている機能を示していますscanf); リクエストに含まれるバイト数がわかると、そのバイト数を正確にブロック読み取りできます。これは、両側に実装するための簡単なプロトコルであるため、簡単な実践的な演習になります。

HTTPは、類似しているがはるかに複雑なプロトコルを使用します(そして、すべての不必要に複雑なプロトコルと同様に、誤解のために相互運用性のバグが一般的でした)が、本質的には同じです。送信者はメッセージの長さを示す必要があります(またはメッセージの本文(HTTPの場合)は、Content-Length:ヘッダーを使用して行います。ただし、すべてを送信する前に送信するバイト数を知ることは必ずしも便利ではないため、HTTPでは「チャンク」エンコーディング(別のヘッダーで示される)が許可されます。その場合、各チャンクは長さ(16進数)、\r\n本文、本文、\r\n...で構成されます。詳細については、RFCを参照してください。\nここでの問題には、一部のクライアントが代わりに送信するという事実が含まれます\r\n末尾の処理方法が少しあいまいであること\r\n。djbが指摘しているように、Netstringsははるかに単純だったでしょう。

完全なHTTPクライアント/サーバーライブラリを使用する場合を除いて、プロセス間通信を実装するためのより実用的な代替手段は、Googleのオープンソースのprotobufパッケージです。以前の私の意見では、技術的に優れたソリューションは、残念ながら便利なオープンソースツールのセットを持っていませんが、ASN.1です(ただし、すぐにそのサイトに飛び込まないでください。それは大きなものです)。

于 2012-12-03T15:15:13.093 に答える
1

ここで最も可能性の高い問題は、ストリームstdinstdoutストリームの両方がデフォルトでラインバッファリングtrされているため、プロセスが機能しており、入力を取得していない/ストリームをパイプにフラッシュしていないことです。子プロセスにさらに入力を送信してみてください。応答が表示されますが...

  • 文字列ゼロのターミネータに注意してください。現在、パイプから読み取られたバイトを出力していますが、これは適切なCスタイルのゼロで終了する文字列ではない可能性があります。
  • write(2)、などのすべてのシステムコールの戻り値を確認します
  • 競合状態を回避します。現在、親と子の両方が入力の待機をブロックされているため、非ブロックモードに切り替えてselect(2)、IO多重化に使用することをお勧めします。
于 2012-11-30T17:43:06.633 に答える
1

trバッファリングされた入力を使用するため、読み取り時にブロックされます。

これ以上書きたくない場合は、書き終えたら(そして読む前に)パイプを閉じてください。

于 2012-11-30T17:55:19.117 に答える
0

write(pipe1[1], s, strlen(s));NUL文字を書き込みませんが、これは次の場合に必要になります。while((*s = toupper(*s))) ++s;

于 2012-11-30T17:26:31.410 に答える