6

私は現在、Linux x86_64を使用してサーバーアプリケーションを作成しています<sys/socket.h>。を介して接続を受け入れた後、取得したソケットをストリームにラップするためaccept()に使用します。fdopen()FILE*

そのストリームへの書き込みとそのストリームからの読み取りはFILE*通常非常にうまく機能しますが、空でない読み取りバッファーがあるときにソケットに書き込むとすぐにソケットは使用できなくなります。

デモンストレーションの目的で、接続をリッスンし、を使用して入力を1行ずつ読み取りバッファーに読み取るコードを記述しましたfgetc()。行が長すぎてバッファに収まらない場合は、完全には読み取られませんが、代わりに次の反復中に読み取られます。

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

FILE* listen_on_port(unsigned short port) {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in name;
        name.sin_family = AF_INET;
        name.sin_port = htons(port);
        name.sin_addr.s_addr = htonl(INADDR_ANY);
        if(bind(sock, (struct sockaddr*) &name, sizeof(name)) < 0)
                perror("bind failed");
        listen(sock, 5);
        int newsock = accept(sock, 0, 0);
        return fdopen(newsock, "r+");
}

int main(int argc, char** argv) {
        int bufsize = 8;
        char buf[9];
        buf[8] = 0; //ensure null termination

        int data;
        int size;

        //listen on the port specified in argv[1]
        FILE* sock = listen_on_port(atoi(argv[1]));
        puts("New connection incoming");

        while(1) {
                //read a single line
                for(size = 0; size < bufsize; size++) {
                        data = fgetc(sock);
                        if(data == EOF)
                                break;
                        if(data == '\n') {
                                buf[size] = 0;
                                break;
                        }
                        buf[size] = (char) data;
                }

                //check if the read failed due to an EOF
                if(data == EOF) {
                        perror("EOF: Connection reset by peer");
                        break;
                } else {
                        printf("Input line: '%s'\n", buf);
                }

                //try to write ack
                if(fputs("ack\n", sock) == EOF)
                        perror("sending 'ack' failed"); 

                //try to flush
                if(fflush(sock) == EOF)
                        perror("fflush failed");        
        }

        puts("Connection closed");
}

コードは、特別なパラメーターなしでgccでコンパイルする必要があります。ポート番号を引数として実行し、netcatを使用してローカルに接続します。

これで、8文字より短い文字列を送信しようとすると、これは問題なく実行されます。ただし、10文字を超える文字列を送信すると、プログラムは失敗します。このサンプル入力:

ab
cd
abcdefghij

この出力を作成します:

New connection incoming
Input line: 'ab'
Input line: 'cd'
Input line: 'abcdefgh'
fflush failed: Illegal seek
EOF: Connection reset by peer: Illegal seek
Connection closed

ご覧のとおり、(正しく)abcdefghの最初の8文字のみが読み取られますが、プログラムが「ack」文字列(クライアントが受信することはありません)を送信しようとすると、Illegal seekエラーが発生し、次の呼び出しfgetc()はEOFを返します。

fflush()パーツがコメントアウトされている場合でも、同じエラーが発生しますが、

fflush failed: Illegal seek

サーバー出力に行がありません。

fputs(ack)パーツがコメントアウトされている場合、すべてが意図したとおりに機能しているように見えますが、gdbから手動で呼び出されたperror()は、「不正なシーク」エラーを報告します。

fputs(ack)との両方fflush()がコメントアウトされている場合、すべて意図したとおりに機能します。

残念ながら、この問題に関する優れたドキュメントやインターネットでの議論は見つかりませんでした。ご協力いただければ幸いです。

編集

私が最終的に解決した解決策は、とを使用しないことです。これは、ソケットfdをモードで確実に使用できるに変換するクリーンな方法がないように思われるためです。代わりに、私はソケットfdに直接取り組み、との独自の置換コードを記述しました。fdopen()FILE*FILE*r+fputsfprintf

誰かがそれを必要とするならば、ここにコードがあります

4

4 に答える 4

5

明らかに、「r +」(読み取り/書き込み)モードは、この実装のソケットでは機能しません。基礎となるコードは、読み取りと書き込みを切り替えるシークを実行する必要があると想定しているためです。これは、stdioストリームの一般的なケースです(何らかの同期操作を行う必要があります)。これは、Dim Timeに戻ると、実際のstdio実装には、ストリームごとに1つのカウンターしかなく、「残りの文字数」のカウンターであったためです。マクロを介してストリームバッファから読み取るにはgetc」(読み取りモードの場合)または「マクロを介してストリームバッファに安全に書き込むことができる文字数putc(書き込みモードの場合)。その単一のカウンタをリセットするには、シークを実行する必要がありました-タイプ操作。

パイプとソケットでのシークは許可されていません(「ファイルオフセット」はそこでは意味がないため)。

1つの解決策は、ソケットをstdioでラップしないことです。もう1つ、おそらくあなたの目的にとってより簡単/より良いのは、1つではなく2つのstdioストリームでラップすることです。

FILE *in = fdopen(newsock, "r");
FILE *out = fdopen(newsock, "w");

ただし、ここには別の欠陥がありますfclose。これは、一方のストリームに移動すると、もう一方のファイル記述子が閉じられるためです。これを回避するにdupは、ソケット記述子を1回実行する必要があります(上記の2つの呼び出しのいずれでも、どちらでもかまいません)。

selectある時点でソケットでまたは同様のものを使用する場合はpoll、stdioバッファリングを追跡するためのクリーンでポータブルな方法がないため、通常は「stdioでラップしない」ソリューションを選択する必要があります。(実装固有の方法があります)。

于 2012-04-21T07:33:14.067 に答える
1

fflush()ネットワークソケットでは使用しないでください。それらはバッファリングされていないストリームです。

また、このコード:

//read a single line
for(size = 0; size < bufsize; size++) {
    data = fgetc(sock);
    if(data == EOF)
        break;
    if(data == '\n') {
        buf[size] = 0;
        break;
    }
    buf[size] = (char) data;
}

1行を読み取りません。8として定義したバッファサイズまでしか読み取りません。まだ、を使用してストリームに書き込む前にsock受信する必要のある受信データがあります。ところで、あなたはそのブロック全体を次のように置き換えることができますfputs

fgets(buf, bufsize, sock);
于 2012-04-21T03:07:42.693 に答える
1

はい、少なくともLinuxでは、1つのファイルストリームを使用してソケットを処理できます。ただし、注意が必要です。エラーをテストするには、ferror()のみを使用する必要があります。これを使用して、フランスの主要なサイトの本番環境で問題なく実行されるコードがいくつかあります。

errnoまたはperror()を使用すると、ストリームを非表示にしたい場合でも、ストリームで発生する内部エラーをキャッチできます。そして「違法シーク」もその一つです。

また、実際のEOF条件をテストするには、feof()を使用する必要があります。これは、trueを返す場合、ferror()がゼロ以外の値を返すことと相互に排他的であるためです。これは、fgetc()を使用する場合、エラーを実際のEOF条件と区別する手段がないためです。したがって、別のユーザーが指摘したように、おそらくfgets()を使用する方がよいでしょう。

だから、あなたのテスト:

if(data == EOF) {
    perror("EOF: Connection reset by peer");
    break;
} else {
    printf("Input line: '%s'\n", buf);
}

次のように書く必要があります:

int sock_error = ferror(sock);
if (sock_error) {
    fprintf(stderr, "Error while reading: %s", strerror(sock_error));
} else {
    printf("Input line: '%s'\n", buf);
}
于 2015-04-21T04:54:41.057 に答える
0

これを試して :

#define BUFSIZE 88

FILE* listen_on_port(unsigned short port) {
 ... 
}

int main(int argc, char** argv) {
    int bufsize = BUFSIZE;
    char buf[ BUFSIZE ];
于 2014-12-15T10:48:04.483 に答える