2012-04-21 35 views
6

Linux x86_64のサーバーアプリケーションを現在<sys/socket.h>を使用して書いています。 accept()で接続を受け入れると、fdopen()を使用して、取得したソケットをFILE*ストリームにラップします。空でない読み取りバッファーでソケットストリームを処理するときに「不正なシーク」エラーが発生しました

通常、そのFILE*ストリームに書き込んだり読み込んだりすると、かなりうまく動作しますが、空の読み取りバッファがないうちに書き込むとすぐに使用できなくなります。

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

#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()の両方がコメントアウトされている場合は、すべてとなります。

残念ながら、私はこの問題に関する適切な文書やインターネットディスカッションを見つけることができませんでしたので、あなたの助けに感謝します。確実r+モードで使用することができますFILE*にソケットのfd変換のないクリーンな方法はないように思えるので、

編集

私は最終的にのために定住ソリューションは、ない使用fdopen()FILE*にあります。 代わりに、自分自身の置換コードをfputsfprintfと書いて、ソケットfdで直接作業しました。

誰でも必要ならば、here is the code

答えて

4

とそのブロック全体を置き換えることができ、「rは+」(読み取り/書き込み)モードは、この実装では、ソケット上では動作しません、間違いなく、基礎となるコードは切り替えることが追求しなければならないことを前提としているので読み書き。 Dim Timeでは、実際のstdioの実装はストリームごとに1つのカウンタしか持たず、 "文字の数が残っています"というカウンタだったので、これはstdioストリームの一般的なケースです(何らかの種類の同期操作を行う必要があります) "getcマクロ"(読み込みモード)または "putcマクロ(書き込みモード)でストリームバッファに安全に書き込むことができる文字数"を介してストリームバッファから読み込むことができます。操作型求める。

が(そこには意味がありません「ファイルオフセット」以降)パイプやソケットに許可されていません。

一つの解決策は全く標準入出力とソケットをラップすることはありません。もう一つ、おそらく簡単にシーク/あなたの目的のためにより良い、それで包むことです、ではなくね、しかし標準入出力ストリーム:

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

あなたはfclose一つのストリームに行くとき、それは他のファイルディスクリプタをクローズするため、別の欠陥は、けれどもここにあります。この問題を回避するには、dupソケット記述子を一度必要とします(上記の2つの呼び出しのどちらでも、どちらにしても問題ありません)。

あなたはいくつかの点でselectまたはpollまたはソケットで同様のを使用する場合は、一般的にstdioのバッファリングを追跡する一切の素敵なきれいなポータブルな方法がありませんから、解決策「はstdioをラップしていない」のために行く必要があります。 (実装固有の方法があります)。

#define BUFSIZE 88 

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

int main(int argc, char** argv) { 
    int bufsize = BUFSIZE; 
    char buf[ BUFSIZE ]; 
+0

したがって、 'FILE * streams'、特に** single **' FILE * stream'を使用するには、きれいな方法がありませんか?ソケットディスクリプタを 'FILE * stream'にラップする前に、' printf() 'と' puts() 'のための[私自身の](http://pastebin.com/LYZGn9c2)ラッパーを持っていましたが、醜い。結局のところ、最もクリーンなソリューションだとします。 –

+0

はい。おそらく皮肉なことに、あなたの「基本的なasprintf」コードは、まさに私がやったように 'snprintf'の戻り値を定義したときの私の心にありました。 :-) – torek

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; 
} 

は、単一の行を読んでいません。バッファサイズは8までと定義されていますが、でに書き込む前に受け取るデータはです。ところであなたは明らかに

fgets(buf, bufsize, sock); 
+0

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

は、のように記述されるべきか? – gcbenison

+0

@gcbenison: 'fgets()'は 'n-1'文字以下を読み込みます。この場合は' bufsize'が終端のゼロバイトのために予約されています。最初の改行はスペースがなくなる前に発生します。 – torek

+0

**書き込みの前に読み込みバッファ**を単に空にするだけで、この問題は解決します。しかし、ユーザー入力の処理にかなりの時間がかかり、新しいメッセージが到着して読み込みバッファが再び満たされ、処理が完了したら、私は返信を送り、その間に新しいデータが到着した?私は他の '不正な探索 'エラーが出ます。 –

0

はこれを試してみてください。 しかし、注意する必要があります。エラーをテストするためにferror()を使用する必要があります。私はこれを使用し、フランスの大手サイトのプロダクションで完璧に動作するコードをいくつか持っています。

errnoまたはperror()を使用すると、ストリームが遭遇するすべての内部エラーをキャッチします(たとえそれを隠したい場合でも)。そして、「違法捜索」もその一つです。

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

だから、あなたのテスト: `関数fgets()は`改行で読むのをやめます

int sock_error = ferror(sock); 
if (sock_error) { 
    fprintf(stderr, "Error while reading: %s", strerror(sock_error)); 
} else { 
    printf("Input line: '%s'\n", buf); 
} 
0

はい、あなたは少なくともLinux上で、あなたのソケットを処理するために、一つのファイルストリームを使用することができます。

関連する問題