1

ゾンビ プロセスの処理に問題があります。プレイヤー間の三目並べの試合を作成する単純なサーバーを作成しました。select() を使用して、接続されている複数のクライアント間で多重化しています。クライアントが 2 つある場合は常に、サーバーは、マッチ アービター プログラムを実行する別のプロセスをフォークします。

問題は、select() がブロックすることです。したがって、子プロセスとして実行されている一致アービター プログラムがあり、それが終了した場合、select() がブロックされているため、着信接続がない場合、親は子プロセスを待機しません。

私はここに私のコードを持っています。

while(1) {
    if (terminate)
        terminate_program();
    FD_ZERO(&rset);
    FD_SET(tcp_listenfd, &rset);
    FD_SET(udpfd, &rset);
    maxfd = max(tcp_listenfd, udpfd);

    /* add child connections to set */
    for (i = 0; i < MAXCLIENTS; i++) {
        sd = tcp_confd_lst[i];
        if (sd > 0)
            FD_SET(sd, &rset);
        if (sd > maxfd)
            maxfd = sd;
    }

    /* Here select blocks */
    if ((nready = select(maxfd + 1, &rset, NULL, NULL, NULL)) < 0) {
        if (errno == EINTR)
            continue;
        else
            perror("select error");
    }

    /* Handles incoming TCP connections */
    if (FD_ISSET(tcp_listenfd, &rset)) {
        len = sizeof(cliaddr);
        if ((new_confd = accept(tcp_listenfd, (struct sockaddr *) &cliaddr, &len)) < 0) {
            perror("accept");
            exit(1);
        }
        /* Send connection message asking for handle */
        writen(new_confd, handle_msg, strlen(handle_msg));
        /* adds new_confd to array of connected fd's */
        for (i = 0; i < MAXCLIENTS; i++) {
            if (tcp_confd_lst[i] == 0) {
                tcp_confd_lst[i] = new_confd;
                break;
            }
        }
    }

    /* Handles incoming UDP connections */
    if (FD_ISSET(udpfd, &rset)) {

    }

    /* Handles receiving client handles */
    /* If client disconnects without entering their handle, their values in the arrays will be set to 0 and can be reused. */
    for (i = 0; i < MAXCLIENTS; i++) {
        sd = tcp_confd_lst[i];
        if (FD_ISSET(sd, &rset)) {
            if ((valread = read(sd, confd_handle, MAXHANDLESZ)) == 0) {
                printf("Someone disconnected: %s\n", usr_handles[i]);
                close(sd);
                tcp_confd_lst[i] = 0;
                usr_in_game[i] = 0;
            } else {
                confd_handle[valread] = '\0';
                printf("%s\n", confd_handle); /* For testing */
                fflush(stdout);
                strncpy(usr_handles[i], confd_handle, sizeof(usr_handles[i]));
                for (j = i - 1; j >= 0; j--) {
                    if (tcp_confd_lst[j] != 0 && usr_in_game[j] == 0) { 
                        usr_in_game[i] = 1; usr_in_game[j] = 1;
                        if ((child_pid = fork()) == 0) {
                            close(tcp_listenfd);
                            snprintf(fd_args[0], sizeof(fd_args[0]), "%d", tcp_confd_lst[i]);
                            snprintf(fd_args[1], sizeof(fd_args[1]), "%d", tcp_confd_lst[j]);
                            execl("nim_match_server", "nim_match_server", usr_handles[i], fd_args[0], usr_handles[j], fd_args[1], (char *) 0);
                        }
                        close(tcp_confd_lst[i]); close(tcp_confd_lst[j]);
                        tcp_confd_lst[i] = 0; tcp_confd_lst[j] = 0;
                        usr_in_game[i] = 0; usr_in_game[j] = 0;
                    }
                }
            }
        }
    }
}

select() がブロックされている場合でも実行を待機できる方法はありますか? それらは非同期であるため、できればシグナル処理なしで。

EDIT:実際には、selectにはタイムアウトを指定できるtimevalデータ構造があることがわかりました。それを使用するのは良い考えでしょうか?

4

2 に答える 2

3

あなたの選択肢は次のとおりだと思います:

  1. すべての子記述子をグローバル配列に保存し、シグナル ハンドラから wait() を呼び出します。メインループで子の終了ステータスが必要ない場合は、これが最も簡単だと思います。

  2. select の代わりに、pselect を使用します。指定された (一連の) シグナル (この場合は SIGCHLD) を受信すると返されます。次に、すべての子 PID で wait/WNOHANG を呼び出します。pselect() の前後の適切なタイミングでSIGCHLDをブロック/ブロック解除する必要があります。

  3. セカンダリ スレッドから子 PID を待機/クリーンアップします。私はこれが最も複雑な解決策(スレッド間の再同期)だと思いますが、あなたが尋ねたので、技術的には可能です。

于 2015-03-25T00:41:39.980 に答える
2

ゾンビ プロセスを防止したいだけなら、SIGCHLDシグナル ハンドラを設定できます。実際にステータスが返されるのを待ちたい場合は、シグナル ハンドラからバイトをパイプ (場合によっては非ブロッキング) に書き込み、selectループ内でそれらのバイトを読み取ることができます。

の処理方法については、 http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.htmlSIGCHLD参照してください。while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {}

おそらく最良の方法は、SIGCHLDシグナル ハンドラからメインselectループに 1 バイトを送信し (念のため非ブロッキング) 、パイプからバイトを読み取ることができるときにwaitpidループ内でループを実行することです。select

signalfdファイル記述子を使用してSIGCHLDシグナルを読み取ることもできますが、これは Linux でのみ機能します。

于 2015-03-25T00:27:26.190 に答える