使用 select() 进行多路复用时等待子进程
Waiting for child processes when using select() for multiplexing
我在处理僵尸进程时遇到了一些麻烦。我写了一个简单的服务器来创建玩家之间的井字游戏比赛。我正在使用 select() 在多个连接的客户端之间进行多路复用。每当有两个客户端时,服务器将派生另一个执行匹配仲裁程序的进程。
问题是 select() 块。因此,假设有一个匹配仲裁程序 运行ning 作为子进程并退出,如果没有传入连接,父进程将永远不会等待子进程,因为 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] = '[=11=]';
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 有一个时间数据结构,我们可以指定超时。使用它是个好主意吗?
如果您只想防止僵尸进程,您可以设置一个 SIGCHLD
信号处理程序。如果您想实际等待 return 状态,您可以从信号处理程序将字节写入管道(非阻塞,以防万一),然后在 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
循环(非阻塞,以防万一)并执行 waitpid
循环在 select
循环中,当可以从管道中读取字节时。
您也可以使用 signalfd
文件描述符来读取 SIGCHLD
信号,尽管这仅适用于 Linux.
我认为你的选择是:
将所有 child 描述符保存在全局数组中,并从信号处理程序调用 wait()。如果你在主循环中不需要 children 的退出状态,我认为这是最简单的。
而不是 select,使用 pselect——它会 return 在收到指定的(一组)信号后,在你的情况下, 发出信号。然后对所有 child 个 PID 调用 wait/WNOHANG。您将需要在正确的时刻 block/unblock SIGCHLD before/after pselect(),请参阅此处:http://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html
等待来自辅助线程的 on/cleanup child PID。我认为这是最复杂的解决方案(线程之间的同步),但既然你问了,它在技术上是可行的。
我在处理僵尸进程时遇到了一些麻烦。我写了一个简单的服务器来创建玩家之间的井字游戏比赛。我正在使用 select() 在多个连接的客户端之间进行多路复用。每当有两个客户端时,服务器将派生另一个执行匹配仲裁程序的进程。
问题是 select() 块。因此,假设有一个匹配仲裁程序 运行ning 作为子进程并退出,如果没有传入连接,父进程将永远不会等待子进程,因为 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] = '[=11=]';
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 有一个时间数据结构,我们可以指定超时。使用它是个好主意吗?
如果您只想防止僵尸进程,您可以设置一个 SIGCHLD
信号处理程序。如果您想实际等待 return 状态,您可以从信号处理程序将字节写入管道(非阻塞,以防万一),然后在 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
循环(非阻塞,以防万一)并执行 waitpid
循环在 select
循环中,当可以从管道中读取字节时。
您也可以使用 signalfd
文件描述符来读取 SIGCHLD
信号,尽管这仅适用于 Linux.
我认为你的选择是:
将所有 child 描述符保存在全局数组中,并从信号处理程序调用 wait()。如果你在主循环中不需要 children 的退出状态,我认为这是最简单的。
而不是 select,使用 pselect——它会 return 在收到指定的(一组)信号后,在你的情况下, 发出信号。然后对所有 child 个 PID 调用 wait/WNOHANG。您将需要在正确的时刻 block/unblock SIGCHLD before/after pselect(),请参阅此处:http://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html
等待来自辅助线程的 on/cleanup child PID。我认为这是最复杂的解决方案(线程之间的同步),但既然你问了,它在技术上是可行的。