2015-03-25 28 views
1

我在处理僵尸进程时遇到了一些问题。我写了一个简单的服务器,它可以创建玩家之间的tic tac toe比赛。我正在使用select()在多个连接的客户端之间进行多路复用。只要有两个客户端,服务器就会分叉另一个执行匹配仲裁程序的进程。使用select()进行多路复用时等待子进程

问题是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()阻塞时也允许等待运行?由于它们是异步的,所以最好不要处理信号

编辑:其实,我发现select有一个timeval数据结构,我们可以指定超时。将使用这是一个好主意?

+0

在select()语句中使用timeout参数(最后一个参数)。然后select后的下一条指令应该是检查发生的超时。检查超时的一种方法是检查输入fd_set是否全为零(或检查目标fd条目是否为零) – user3629249 2015-03-25 05:21:26

+0

否!不应只使用超时参数并检查是否发生超时。如果超时为2秒,程序每秒接收一块数据,那么超时不会发生。当你从select返回时,你应该在所有情况下运行定时代码,而不仅仅是在超时情况下。如果你想让代码每秒运行一次,你可以存储它的最后执行时间,然后检查cur_time() - last_exec_time是否大于一秒。 – juhist 2015-03-25 09:04:45

回答

2

如果你只是想防止僵尸进程,你可以设置一个SIGCHLD信号处理程序。如果您想实际等待返回状态,则可以从信号处理程序中将字节写入管道(非阻塞,以防万一),然后读取select循环中的那些字节。

对于如何处理SIGCHLD,看到http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html - 你想要做的事就像while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {}

也许最好的方法是发送从SIGCHLD信号处理一个字节到主select环路(非阻塞,只在情况下)并在select循环中执行waitpid循环时,可以从管道读取字节。

您也可以使用signalfd文件描述符来读取SIGCHLD信号,尽管这只适用于Linux。

3

我觉得你的选择是:

  1. 保存所有的孩子在一个全局数组描述符和调用wait()从信号处理程序。如果你不需要主循环中你的孩子的退出状态,我认为这是最简单的。

  2. 而不是选择,使用pselect - 它会在收到指定(一组)信号后返回,在你的情况下,SIGCHLD。然后在所有子PID上调用wait/WNOHANG。您需要在pselect()之前/之后的正确时刻阻止/解锁SIGCHLD,请参见:http://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html

  3. 等待/清除辅助线程中的子PID。我认为这是最复杂的解决方案(线程间重新同步),但是既然你问了,这在技术上是可行的。

+0

在父进程将套接字传递给子进程并且打算接受来自子进程的连接的情况下,可以使用方法2,同时处理由于错误导致子进程退出的可能性在连接到插座之前?这将允许我处理子进程失败和子进程成功与挂起的套接字连接。 – CMCDragonkai 2017-01-21 13:33:41