4

select()とaccept()を使用してIPv4TCP接続を受け入れる簡単なアプリケーションを作成しました。

これをテストするためにPythonスクリプトを使用します。100個の接続を順番に開きます。すなわち:

for i in range(100):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print s.connect((IP, PORT))
    s.send("Test\r\n")

私が観察しているのは、最初のX接続後2秒間、アプリケーションがselect()でスタックしていることです。straceからの出力:

1344391414.452208 select(30, [3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29], NULL, NULL, NULL) = 1 (in [3])
1344391416.742843 accept(3, 0, NULL)    = 30

私のコードは次のとおりです。私が間違っていることについて何か考えはありますか?

#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>

int
fd_create (void)
{
    int fd;
    int set = true;
    struct sockaddr_in addr;

    fd = socket(AF_INET, SOCK_STREAM, 0);

    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1999);
    addr.sin_addr.s_addr = INADDR_ANY;

    bind(fd, (struct sockaddr *)&addr, sizeof(addr));

    listen(fd, 1024);

    return (fd);
}

int
fd_echo (int fd)
{
    int n;
    char buffer[128 + 1];

    while ((n = recv(fd, buffer, 128, 0)) > 0);

    return (n);
}

int
main (void)
{
    int listen_fd;
    fd_set working;
    fd_set master;
    int max_fd;
    int i;
    int new_fd;
    int rc;
    int con;

    FD_ZERO(&master);
    listen_fd = fd_create();
    fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL) | O_NONBLOCK);

    max_fd = listen_fd;
    printf("%d\n", listen_fd);
    FD_SET(listen_fd, &master);
    con = 0;
    for (;;) {
    memcpy(&working, &master, sizeof(fd_set));
    select(max_fd + 1, &working, NULL, NULL, NULL);

    for (i = 0; i <= max_fd; i++) {
        if (FD_ISSET(i, &working)) {
        if (i == listen_fd) {
            while ((new_fd = accept(i, NULL, NULL)) >= 0) {
            fcntl(new_fd, F_SETFL, fcntl(new_fd, F_GETFL) | O_NONBLOCK);
            FD_SET(new_fd, &master);
            if (max_fd < new_fd) {
                max_fd = new_fd;
            }
            printf("New connection %d (%d)\n", new_fd, ++con);
            }
            if ((new_fd == -1) && (errno != EAGAIN && errno != EWOULDBLOCK)) {
            return(0);
            }
        } else {
            rc = fd_echo(i);
            if ((rc == 0) ||
            ((rc == -1) && ((errno != EAGAIN && errno != EWOULDBLOCK)))) {
            close(i);
            FD_CLR(i, &master);
            }
        }
        }
    }
    }
    return (0);
}
4

2 に答える 2

3

更新/警告:この答えが当てはまることを証明しようとしているときに、おそらく当てはまらないことがわかりました。テストを実行したところ、max_fdが300を超えることなく遅延が発生しました。また、poll()でも遅延が発生しました。そこで、tcpdumpを試してみたところ、再送信がありました。127.0.0.1でさえ、これほど速くパケットを投げると、パケットをドロップできるようです。それが最も差し迫った問題でなくても、それは本当の問題であるため、ここに答えを残します。

したがって、これには多くのファイル記述子が含まれ、ポーリングでは機能しますが、選択では機能しません。それらの手がかりで私は説明を見ることができます:あなたはFD_SETSIZE限界を超えました。

POSIXからの公式の発表は(FD_ZERO/ FD_SET/ FD_CLR/を参照FD_ISSET):

fd引数が0未満またはFD_SETSIZE以上の場合、fdが有効なファイル記述子でない場合、または引数のいずれかが副作用のある式である場合、これらのマクロの動作は未定義です。

http://pubs.opengroup.org/onlinepubs/9699919799/functions/select.htmlから)

何が起こったのかを実際に理解するには、fd_setタイプの実際の実装を公式の仕様よりも深く調べる必要があります。それは分裂した性格を持っています。が実装されているカーネルでselectは、ビットの可変長配列として扱われます。の最初の引数selectは、配列が終了する場所を決定するために使用されます。カーネルを呼び出すとselect(2048, ...)、NULL以外の各fd_set *配列が256バイト(2048ビット)の配列を指すことが期待されます。

しかし、ユーザースペースでfd_setは、は固定サイズの構造体です。サイズはFD_SETSIZEビットです。これは私のシステムでは1024であり、おそらくあなたのシステムでも同様です。FD_SET他のマクロは基本的に配列の要素への割り当てを行っているだけですが、個々のビットである概念的な配列要素を処理する必要があるため、少し複雑です。したがって、ファイル記述子の1つが1024であり、FD_SETそれを実行しようとすると、同等の処理が実行されます。

int array[1024];
array[1024] = 1;

言い換えると、の後にメモリにあるものをすべて壊して、fd_set後で奇妙なことが起こりました。

これを回避する方法があります。#define FD_SETSIZE somebignumberを定義するヘッダーを含める前に実行する古いコードを見てきましたfd_set。どのOSで動作したのかわかりません。試したところ、glibcはそれを無視しているようです。

より良い可能性は、古い「構造体ハック」のように、構造体よりも多くのメモリを割り当てる構造体を実行することです。sizeof追加のメモリは、構造体の最後のメンバーである配列の追加要素として使用できます。

fd_set *rfds = malloc(128+sizeof *foo); /* can hold fds up to FD_SETSIZE+128*8-1 */

もちろん、使い終わったら解放することを忘れないでください。toとマクロrfdsの代わりに&rfdsパスし、の代わりに独自の方法を実行してください。カーネルの実装が変更されないことを願っていますそれとおしゃべり。しかし、それは機能します...今のところ。selectFD_*memsetFD_ZERO

ポーリングを使用することは、実際にはおそらく正しい答えです。

于 2012-08-09T07:18:48.133 に答える
1

カーネルのさらなるデバッグ...

「sk_acceptq_is_full(sk)」が true を返すため、パケットは「tcp_v4_syn_recv_sock()」でドロップされます。

"sk->sk_ack_backlog" は 11 で、構成済みの "sk->sk_max_ack_backlog" は 10 です (これは listen() コマンドで設定します)。

(EJP ノートに基づいて更新します。) だから、何が起こっていると思います:

クライアントは connect() でブロックします。SYN がサーバーに送信されます。カーネルは SYN を取得し、SYN/ACK を送信します。クライアントは SYN/ACK を受け取り、ブロックを解除し、a) ACK を送信し、b) 新しい SYN/ACK を送信します。

サーバーは ACK を受信し、接続をバッ​​クログに入れます。

それを10回やると行き詰まります。

良い人。あなたの助けがなければ、それを理解することはできなかっただろう. ありがとう!!

于 2012-08-10T04:07:38.983 に答える