5

私はCとTCPサーバーの作成にかなり慣れてrecv()いないため、サーバーが応答するコマンドを送信するクライアントからの s を処理する方法を考えていました。この質問のために、ヘッダーが 1 番目のバイト、コマンド識別子が 2 番目のバイト、ペイロードの長さが 3 番目のバイトであり、その後にペイロード (存在する場合) が続くとしましょう。

recv()このデータへの最善の方法は何ですか? 最初の 3 バイトをバッファーに読み込むために呼び出しrecv()、ヘッダーとコマンド識別子が有効であることを確認してから、ペイロードの長さを確認recv()し、ペイロードの長さを長さとして再度呼び出し、これを前述のバッファーの後ろに追加することを考えていました。ただし、 Beej のネットワーキングに関する記事 (特に「Son of Data Encapsulation 」セクション) を読んで、次のパケットの一部を取得するなどの状況を処理するには、「2 つの [最大長] パケットに十分な大きさの配列」を使用するようアドバイスしています。

これらのタイプの を処理する最善の方法は何recv()ですか? 基本的な質問ですが、発生する可能性のあるすべてのケースを処理して、効率的に実装したいと思います。前もって感謝します。

4

4 に答える 4

9

Beej がほのめかし、AlastairG が言及している方法は、次のように機能します。

同時接続ごとに、読み取り済みでまだ処理されていないデータのバッファーを維持します。(これは、Beej が最大パケット長の 2 倍のサイジングを提案しているバッファです)。明らかに、バッファは空から始まります:

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()関数は、有効なヘッダーとコマンド バイトをチェックします)。

短い長さを返すことができるため、この種の手法が必要になります。提案された方法では、ペイロードの長さが 500 であるが、次 400バイトしか返されない場合はどうなりますか? いずれにせよ、次回ソケットが読み取り可能になるまで、これらの 400 バイトを保存する必要があります。recv()recv()

複数の同時クライアントを処理する場合は、クライアントごとに 1 つだけをrecv_buffer用意recv_lenし、それらをクライアントごとの構造体に詰め込みます (これには、クライアントのソケット、おそらくソースアドレス、現在の状態など、他のものも含まれている可能性があります)。

于 2010-12-03T01:29:52.393 に答える
5

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

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

書き込み時に同様のことを行います。つまり、コマンド処理関数がバッファに書き込みます。バッファーにデータがある場合は、ソケットをチェックする (選択またはポーリング) ときに書き込み可能性をチェックし、実際にバッファーから書き込まれたバイトのみを削除することを忘れずに、できるだけ多く書き込みます。

このような状況では、循環バッファーがうまく機能します。

より軽量でシンプルなソリューションがあります。しかし、これは良いものです。サーバーは複数の接続を取得する可能性があり、パケットが分割される可能性があることに注意してください。ソケットからバッファに読み込んで、完全なコマンドのデータを持っていないことがわかった場合、すでに読み込んだデータをどうしますか? どこに保管していますか?その接続に関連付けられたバッファに格納する場合は、最初に説明したように、すべてを独り占めしてバッファに読み込むだけです。

また、このソリューションでは、接続ごとに個別のスレッドを生成する必要がなくなります。実際の問題なしに、任意の数の接続を処理できます。接続ごとにスレッドを生成することは、システム リソースの不必要な浪費です。ただし、複数のスレッドが推奨される特定の状況を除きます。そのためには、ワーカー スレッドを使用して、ソケット処理をシングル スレッドに保ちながら、そのようなブロッキング タスクを実行することができます。

基本的には、Beej の言うことには同意しますが、一度に少しずつ読んではいけません。一度に大きなチャンクを読み取ります。このようなソケット サーバーを作成し、ソケットの経験とマニュアル ページに基づいて学習し、設計することは、私がこれまでに取り組んだプロジェクトの中で最も楽しく、非常に教育的でした。

于 2010-12-02T15:48:38.590 に答える
2

Alastair が説明するソリューションは、パフォーマンスの点で最高です。参考までに - 非同期プログラミングは、イベント駆動型プログラミングとも呼ばれます。言い換えれば、データがソケットに来るのを待ち、それをバッファに読み込み、いつ/何ができるかを処理し、繰り返します。アプリケーションは、データの読み取りと処理の間に他のことを行うことができます。

非常に似たようなことをするのに役立つとわかったリンクがさらにいくつかあります。

2 つ目は、これらすべてを実装するのに役立つ優れたライブラリです。

バッファを使用してできる限り読み取ることに関しては、それは別のパフォーマンスの問題です。一括読み取りの方が優れており、システム コール (読み取り) が少なくなります。処理するのに十分な量があると判断したら、バッファー内のデータを処理しますが、一度に 1 つの「パケット」 (3 バイトのヘッダーで説明したもの) のみを処理し、バッファー内の他のデータを破棄しないようにします。 .

于 2010-12-02T19:24:34.107 に答える
1

複数接続を使用している場合、基本的に 2 つの仮定があり、複数接続を処理する最良の方法 (リッスン ソケット、readfd、または writefd) は、select/poll/epoll を使用することです。要件に基づいて、これらのいずれかを使用できます。

2 番目のクエリでは、複数の 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_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;
    }

したがって、この方法では、固定長の配列を使用する必要がなく、ロジックを簡単に実装できます。

于 2013-08-27T18:53:11.453 に答える