上节发现了新的fork程序还有问题, 本节就来解决这个问题.
问题回顾
将两个客户端关闭, 结果呢, 服务端的两个子进程的资源没有被回收从而成为了僵尸进程了. 父进程没有正确的回收子进程的资源, 所以人家就成了僵尸.

其实在apue书中就讲到过, 子进程退出后父进程会收到子进程的SIGCHLD信号, 但是该信号父进程默认是忽略的. 而现在想要解决这个问题, 就必须让父进程捕捉该信号, 并作出处理.
捕捉信号
既然知道问题所在, 现在就来想办法捕捉信号. 信号捕捉可以使用signal函数也可以使用sigaction函数. 推荐使用后者比较好, 这里我就是用后者.
父进程等待子进程退出有wait和waitpid两个函数, 前者调用后只有被阻塞到有子进程退出, 后者有多个选择还可以可以设置进程不阻塞. 而服务端最好不能被阻塞, 所以我们只有选择用waitpid函数.
实验代码
现在来看我们修改的代码, 完整代码service_fork_SIGCHLD.c .
首先捕捉和处理SIGCHLD信号
// SIGCHLD的信号处理
void sighandler(int signo)
{
if(signo == SIGCHLD)
{
pid_t pid;
// 一定要死循环
while(1)
{
// waitpid设置为WNOGANG, 非阻塞
if((pid = waitpid(-1, NULL, WNOHANG)) <= 0)
break;
printf("child %d terminated\n", pid);
}
}
int main(int argc, char *argv[])
{
struct sigaction action;
action.sa_handler = sighandler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
if(sigaction(SIGCHLD, &action, NULL) != 0)
EXIT("sigaction");
}
信号处理中要注意两点 :
- 一定要死循环. UNIX信号默认不排队, 多个相同信号到来最后只会处理一次信号
- waitpid要设置为
WNOHANG. 让waitpid不阻塞, 因为随时都可能有未终止的子进程在运行.
最后看服务端的修改 :
// 服务端
int service(int port, const char *ser_addr)
{
while(1)
{
clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
// accept被信号打断后重新运行
if(clientfd < 0)
{
if(errno == EINTR)
continue;
else
EXIT("accept");
}
...
}
close(sockfd);
return 0;
}
因为accept在阻塞等待连接信号到来的时候可能被中断打断(比如信号), 导致errno设置为了EINTR, 但是这种错误又不是致命的, 所以判断是中断打断就重新阻塞就行了.
服务端 :
./a.out 1 8080 127.0.0.1
客服端 :
./a.out 2 8080 127.0.0.1

现在重新运行就发现服务端的问题解决了.
总结
解决了fork带来的问题后, 接下来我们继续来解决客服端的问题. 解决了fork带来的问题后, 接下来我们继续来解决客服端的问题. 当然也可能并没有注意到客服端的问题所在, 不急, 下节就能知道.
- accept可能会被中断打断, 但不是致命错误, 需要重启
- 捕捉SIGCHLD信号注意点.

本文详细探讨了在使用fork创建子进程时遇到的僵尸进程问题,并提供了通过捕捉和处理SIGCHLD信号来避免这一问题的有效解决方案。文中通过修改服务端代码,引入信号处理函数,使用waitpid而非wait,确保服务端不会因子进程成为僵尸进程而受到影响。
1571

被折叠的 条评论
为什么被折叠?



