6

【以前も似たようなことを質問しました。これはより焦点を絞ったバージョンです。]

クライアントのソケットの close() を「見る」のではなく、TCP ソケットでのサーバーの select() 呼び出しが一貫してタイムアウトになる原因は何ですか? クライアント側では、ソケットは通常の socket() で作成されたブロッキング ソケットであり、サーバーに正常に接続し、ラウンドトリップ トランザクションを正常に送信します。サーバー側では、ソケットは accept() 呼び出しによって作成され、ブロックされ、fork() によって子サーバー プロセスに渡され、最上位サーバーによって閉じられ、子サーバー プロセスによって正常に使用されます。最初のトランザクション。続いてクライアントがソケットを閉じると、子サーバー プロセスの select() 呼び出しは、ソケットの読み取り準備完了状態を示すのではなく、一貫して (1 分後に) タイムアウトします。select() 呼び出しは、読み取り可能状態のみを探します。

以下は、子サーバー プロセスでの単純化された論理的に同等の select() 使用コードです。

int one_svc_run(
    const int           sock,
    const unsigned      timeout) 
{
    struct timeval      timeo;
    fd_set              fds;

    timeo.tv_sec = timeout;
    timeo.tv_usec = 0;

    FD_ZERO(&fds);
    FD_SET(sock, &fds);

    for (;;) {
        fd_set      readFds = fds;
        int         status = select(sock+1, &readFds, 0, 0, &timeo);

        if (status < 0)
            return errno;

        if (status == 0)
            return ETIMEDOUT;

        /* This code not reached when client closes socket */
        /* The time-out structure, "timeo", is appropriately reset here */
        ...            
    }
    ...
}

以下は、クライアント側での一連のイベントと論理的に同等です (エラー処理は示されていません)。

struct sockaddr_in *raddr = ...;

int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
(void)bindresvport(sock, (struct sockaddr_in *)0);
connect(sock, (struct sockaddr *)raddr, sizeof(*raddr));
/* Send a message to the server and receive a reply */
(void)close(sock);

fork()、exec()、および system() は呼び出されません。コードはこれよりかなり複雑ですが、これは関連する呼び出しのシーケンスです。

Nagel のアルゴリズムにより、close() で FIN パケットが送信されない可能性はありますか?

4

3 に答える 3

4

最も可能性の高い説明は、接続のクライアント側を閉じていると思っているときに、実際には閉じていないということです。おそらく、閉じられていないクライアントソケットを参照する他のファイル記述子があるためです。

クライアント プログラムが a (または fork などの関連する呼び出し) を実行したことがある場合、forkforkされた子はファイル記述子のコピーを持っている可能性があり、これが表示されている動作を引き起こします。systempopen

問題をテスト/回避する 1 つの方法は、ソケットを閉じる前にクライアントに明示的な shutdown(2) を実行させることです。

shutdown(sock, SHUT_RDWR);
close(sock);

これにより問題が解決する場合は、それが問題です。クライアント ソケット ファイル記述子の別のコピーがどこかにぶら下がっています。

問題の原因がソケットの取得である場合、最善の解決策は、ソケットを作成した直後にソケットに close-on-exec フラグを設定することです。

fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC);

または、一部のシステムではSOCK_CLOEXEC、ソケット作成呼び出しにフラグを使用します。

于 2013-03-08T18:09:33.130 に答える
3

謎が解けました。

最初のコメントの @nos は正しかったです。これはファイアウォールの問題です。クライアントによる shutdown() は必要ありません。クライアントはソケットを閉じます。サーバーは正しいタイムアウトを使用します。コードにバグはありません。

この問題は、Linux Virtual Server (LVS) のファイアウォール ルールが原因でした。クライアントは LVS に接続し、その接続はいくつかのバックエンド サーバーの中で最も負荷の低いサーバーに渡されます。クライアントからのすべてのパケットは LVS を通過します。バックエンド サーバーからのすべてのパケットは、クライアントに直接送信されます。LVS のファイアウォール ルールが原因で、クライアントからの FIN パケットが破棄されました。したがって、バックエンド サーバーはクライアントによる close() を確認しませんでした。

解決策は、LVS システムの iptables(8) ルールから「-m state --state NEW」オプションを削除することでした。これにより、クライアントからの FIN パケットをバックエンド サーバーに転送できます。詳細については、こちらの記事をご覧ください。

wireshark(1) の使用を提案してくれた皆さんに感謝します。

于 2013-03-12T22:31:43.863 に答える
1

select()Linux の呼び出しは、timeout引数の値を変更します。マニュアルページから:

Linux では、select() は、スリープしていない時間を反映するようにタイムアウトを変更します。

だからあなたのtimeo意志はゼロになります。そして、ゼロの場合selectはすぐに戻ります(ほとんどの場合、戻り値はゼロです)。

次の変更が役立つ場合があります。

for (;;) {
    struct timeval timo = timeo;
    fd_set      readFds = fds;
    int         status = select(sock+1, &readFds, 0, 0, &timo);
于 2013-03-07T23:19:41.403 に答える