2010-12-02 21 views
4

私はCの新機能とTCPサーバーの作成方法について、サーバーが応答するコマンドを送信するクライアントからrecv()を処理する方法が不思議でした。この質問のために、ヘッダが第1バイト、コマンド識別子が第2バイト、ペイロードの長さが第3バイトであり、ペイロードがあるとします。複数のrecv()呼び出しとすべての可能なシナリオの処理

このデータをrecv()する最良の方法は何ですか?私は、最初の3バイトをバッファに読み込み、ヘッダとコマンド識別子が有効であることを確認し、ペイロードの長さをチェックし、ペイロードの長さをrecv()にもう一度コールするようrecv()を呼び出すことを考えていました。前述のバッファの裏側にある。しかし、Beejのネットワーキング記事(特にこのセクション:http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html#sonofdataencap)を読むと、次のパケットを取得するなどの状況に対処するために、「2つの[max length]パケットに十分な大きさの配列」を使用するようにアドバイスします。

これらの種類のrecv()を処理する最良の方法は何ですか?基本的な質問ですが、効率的に実装したいと思います。前もって感謝します。

答えて

5

Beejはをほのめかしている方法、およびAlastairGは言及は、このようなものに動作します:

の同時接続ごとに、あなたは、読み取りますが、未処理のデータのバッファを維持します。 (これはBeejは二倍最大パケット長にサイジング示唆バッファです)。明らかに、バッファが空から始まり:

unsigned char recv_buffer[BUF_SIZE]; 
size_t recv_len = 0; 

あなたのソケットが読み取り可能であるときはいつでも

、その後すぐにあなたが持っているものを試してみて、プロセス、バッファ内の残りのスペースに読み:

result = recv(sock, recv_buffer + recv_len, BUF_SIZE - recv_len, 0); 

if (result > 0) { 
    recv_len += result; 
    process_buffer(recv_buffer, &recv_len); 
} 

process_buffer()ますを試してみて、パケットとしてバッファにデータを処理します。バッファがまだ完全なパケットが含まれていない場合は、それだけを返す - それ以外の場合は、データを処理してバッファから削除します。

void process_buffer(unsigned char *buffer, size_t *len) 
{ 
    while (*len >= 3) { 
     /* We have at least 3 bytes, so we have the payload length */ 

     unsigned payload_len = buffer[2]; 

     if (*len < 3 + payload_len) { 
      /* Too short - haven't recieved whole payload yet */ 
      break; 
     } 

     /* OK - execute command */ 
     do_command(buffer[0], buffer[1], payload_len, &buffer[3]); 

     /* Now shuffle the remaining data in the buffer back to the start */ 
     *len -= 3 + payload_len; 
     if (*len > 0) 
      memmove(buffer, buffer + 3 + payload_len, *len); 
    } 
} 

do_command()機能が有効なヘッダーとコマンドバイトをチェックします):だから例えば、プロトコルのために、それは次のようになります。

がどのrecv()は短い長さを返すことができるので、この種の技術は、必要が終わる - あなたの提案した方法で、あなたのペイロード長は500ですが、次のrecv()だけあなたに400のバイトを返す場合、何が起こりますか?とにかくソケットが次回読めるようになるまで、これらの400バイトを保存する必要があります。

複数の同時クライアントを処理する場合は、クライアントごとにrecv_bufferrecv_lenという単純な値を設定し、クライアントごとの構造体(クライアントのソケット、おそらくはソースアドレス、現在の状態等。)。

+0

短い読書+1!私は+ sを追加しますが、許可されません。データがパケット境界を越えて分割され、まだ到着していない可能性があります。または、プログラムが信号を受信して​​いる可能性があります。 –

+0

カフェ、ありがとう!コードは常に助けになります。 Beejの説明だけでなく、Alastair'sは今よりはるかに意味をなさない。私はこれがスタック上の他の人にも役立つだろうと確信しています。私はrecvのためにこれに非常に似た何かを実装します。 1つの質問は、行に:result = recv(sock、recv_buffer + recv_len、BUF_SIZE - recv_len、0);あなたは正しくループしていませんか?したがって、たとえば、もしBUF_SIZEが1024ならば、1,024バイトのrecvを実行し、1バイトであってもバッファに入っているものを処理しようとしますか?また、処理するコマンドがたくさんある場合、バッファ上のmemmoveのパフォーマンスヒットはどうなりますか? – Jack

+0

@Jack:そうですね、この回の回では、 'recv(); process_buffer(); 'segment(おそらく' select() 'がブロックされます)。 'process_buffer();'は、1バイトしか持たない場合でも呼び出されます。なぜなら、これは完全なメッセージかどうかをすべてのスマートに1つの場所(この場合は 'process_buffer()すぐにドロップアウトし、 'select()'で終わり、より多くのバイトを待っています)。 – caf

5

良い質問。どのように完璧に行きたいですか?すべてのダンスのすべてのソリューションを歌うには、非同期ソケットを使用し、可能な限りデータを読み込み、新しいデータを取得するたびにバッファ上でデータ処理関数を呼び出します。

これにより、大きな読み取りを行うことができます。多くのコマンドをパイプライン化すると、ソケットで再び待たずに処理できる可能性があり、パフォーマンスと応答時間が向上します。

書き込みで同様のことをします。これは、コマンド処理関数がバッファに書き込むことです。バッファにデータがある場合は、ソケットをチェックする(selectまたはpoll)際に書き込み可能性をチェックし、可能な限り書き込みます。バッファから実際に書き込まれたバイトのみを削除することを覚えておいてください。

このような状況では、循環バッファはうまく動作します。

よりシンプルなソリューションがあります。しかし、これは良いものです。サーバーが複数の接続を取得し、パケットを分割できることに注意してください。ソケットからバッファを読み込み、見つからない場合は、完全なコマンドのデータがあります。既に読み込んだデータはどうしますか?どこに保管しますか?その接続に関連付けられたバッファに格納しておくと、最初の場所で上記のようにバッファ全体を読み込み、バッファに読み込むこともできます。

この解決策では、接続ごとに別個のスレッドを生成する必要もなくなり、実際の問題なしに任意の数の接続を処理できます。接続ごとにスレッドを生成することは、複数のスレッドが推奨される特定の状況を除き、システムリソースの無駄な浪費であり、ソケット処理をシングルスレッドに保ちながら、そのようなブロッキングタスクを実行するワーカースレッドを単純に持つことができます。

基本的に私はBeejが言うことに同意しますが、一度にちょっとしたビットを読まないでください。一度に大きなチャンクを読んでください。このようなソケットサーバーを作成し、私がソケット体験とマニュアルページの小さなビットに基づいて行ったように学習して設計することは、私がこれまでに取り組んだ最も楽しいプロジェクトの1つでした。

+0

ありがとうAlastairG。私は基本的にはできるだけシンプルにして、次のパケットを読み込んだり、部分パケットを読み込んだりするなど、最も一般的な状況を処理しようとしています。私は実際にselectを使用して受信データ(この場合はコマンド)のクライアントソケットを監視していますので、非同期ソケットの問題ではないと思います。私の3バイトの読み込み方法は、ペイロードがうまくいかないでしょうか? Beejが言ったことを実装したくない理由は、実際のコード/擬似コードなしで理解するのが難しいからです。しかし、アドバイスをありがとう:) – Jack

+0

受信データを処理した後の書き込み/送信については、パケットの長さを知っているので、単純にBeejのsendallメソッドのバリエーションを使用しようとしています。http:// beej .us/guide/bgnet/output/html/multipage/advanced.html#sendall。私は読書についてもっと心配していました。 – Jack

+0

書き込みについての私のコメントは完全性のためのものでした。他の誰かが本当に良いソケットサーバーを書く方法を知りたがっている場合に備えてください。私が言うことの1つは、小さくてシンプルなソケットサーバーを作成しようとしたことですが、私が記述する方法でそれを行う必要があることがすぐに分かります。必ずしもそうではありません。 3バイトを読み込むことはできますが、それはなぜそのようになりますか? – AlastairG

2

Alastairが解説しているソリューションは、パフォーマンス面で最適です。 FYI - 非同期プログラミングはイベントドリブンプログラミングとも呼ばれます。つまり、データがソケットに来るのを待ってバッファに読み込み、何時でも処理できるようにしてから、繰り返します。アプリケーションは、データの読み取りと処理の間に他の処理を行うことができます。私は非常によく似た何かをやっ役に立った

カップルより多くのリンク:

二本のすべての実装を支援するための素晴らしいライブラリです。

バッファを使用して、あなたはできる限りを読み取るためとして、それは別のパフォーマンスのことです。一括読み込みはより良く、システムコール(読み込み)は少なくなります。あなたは、あなたが処理するのに十分持っているかを決定する際のバッファにデータを処理しますが、一度に一つだけ、あなたの「パケット」(あなたは3バイトのヘッダーを記載したもの)のを処理することを確認することや、バッファ内の他のデータを破壊しません。

+0

ありがとう、これは助けてください。あなたがより良いパフォーマンスを得るために「できるだけ読む」と言ったら、長さとしてMAX_BUF_SIZEのrecvを1回だけ行うことを意味しますか? (つまり、#define MAX_BUF_SIZE 1024 ... unsigned char buf [MAX_BUF_SIZE] ... recv(sockfd、buf、MAX_BUF_SIZE、0))?それとも別のことを意味しましたか?とにかく、Alastairが説明したように、私はイベント駆動のプログラミングファッションでこれをやっていると思います。最初に3バイトを読み込み、残りのペイロードを読み込む理由は、2番目の呼び出しでrecvに渡すパケットの正確な長さを知っていることです。 – Jack

+0

@Jack:1024は小さいと思っています。ギガビットネットワークでは、最大帯域幅のためにメガバイトのバッファが必要になります。 –

+0

しかし、私のプログラムのために、私はおそらく大きなものは必要ありません。間違いなく私が検討するものです。ありがとう! – Jack

1

2つの仮定は、複数の接続を使用している場合は、複数の接続(リスニングソケット、readfdまたはwritefdかどうか)を処理するための最良の方法は、選択/世論調査/ファイルディスクリプタであるが基本的にあります。これらのいずれかを要件に基づいて使用することができます。

複数のrecv()を処理する方法この方法は、データが到着するたびに のように使用することができます(説明したように固定長とフォーマットでなければなりません)。

buff_header = (char*) malloc(HEADER_LENGTH); 
    count = recv(sock_fd, buff_header, HEADER_LENGTH, MSG_PEEK); 
    /*MSG_PEEK if you want to use the header later other wise you can set it to zero 
     and read the buffer from queue and the logic for the code written below would 
     be changed accordingly*/ 

これによって、ヘッダーが取得され、パラメータを確認して完全なメッセージ長を抽出することもできます。フルMSGの長さがちょうどあなたには、いくつかの固定長の任意の配列を取るために、あなたが簡単にあなたのロジックを実装することができない必要がある。このように、そう

msg_length=payload_length+HEADER_LENGTH; 
    buffer =(char*) malloc(msg_length); 
    while(msg_length) 
    { 
     count = recv(sock_fd, buffer, msg_length, 0); 
     buffer+=count; 
     msg_length-=count; 
    } 

フルMSGを受け取る取得した後 。

関連する問題