僵尸进程处理
客户端正常断开连接,但是服务器不处理SIGCHLD信号,会使服务器子进程死。
设置僵尸进程的目的:
维护子进程信息,以便父进程可以在以后获得它。信息包括子进程ID、终止状态和资源利用信息(CPU时间、内存使用等。).如果一个进程结束,并且有子进程处于dead状态,那么它的所有dead子进程的父进程ID都会变成1(init process),init进程会清理它们,也就是说,init进程会等待它们,并删除它们的dead状态。
僵尸进程的处理方法:
(1)忽略SIGCHLD信号可以防止僵尸进程,在服务器端主函数监听后添加:signal(SIGCHLD,SIG _ IGN);
代码如下:
//用sigaction函数typedef void Sigfunc(int)替换早期unix版本的signal函数的实现;Sigfunc *Signal(int signo,SIG func * func){ struct SIG action act,oactact.sa _ handler = funcsigemptyset(& act . sa _ mask);act . sa _ flags = 0;if(signo = = SIGALRM){ # ifdef SA _ INTERRUPT act . SA _ flags | = SA _ INTERRUPT;#endif} else {#ifdef SA_RESTART //如果系统可以重启中断的系统调用,那么中断的系统调用将由内核重启。sa _ flags | = sa _ restart#endif } if (sigaction(signo,&act,& oact)& lt;0)return(SIG _ ERR);return(oact . sa _ handler);}(2)通过wait/waitpid方法。
//使用Void Onsignalcatch (int信号号){ PID _ tpidpid = wait(空);printf(& # 34;子级%d已终止。\ n & # 34,PID);返回;}在服务器端监听调用后添加:
Signal(SIGCHLD,onSignalCatch);//这个信号处理函数必须在fork的第一个子过程之前完成,程序只会再执行一次。三个客户端正常终止后,服务器端结果如下:
上述方法调用wait在SignalCatch上的函数作为信号处理函数,其正常结束过程如下:
(1)终止客户的EOF类型。客户端TCP向服务器发送FIN,服务器用ACK响应。
(2)接收到客户端FIN的服务器TCP发送给子进程的阻塞读取的EOF后,子进程结束。
(SIGCHLD信号提交时,父进程阻塞accept的调用。执行信号处理函数onSignalCatch,其wait调用子进程pid并打印返回。
(4)信号在阻塞慢速系统调用accept时被父进程捕获,所以内核会让accept返回一个EINTR错误(中断的系统调用)。但是父进程不处理错误,被中断的系统调用重新启动(在上面提到的自定义信号函数中,设置了SA_RESTART标志),所以accept不返回错误。
C/C++ Linux的高级服务器架构师需要学习后台私有信息“素材”(包括C/C++、Linux、golang技术、Nginx、ZeroMQ、MySQL、Redis、fastdfs、MongoDB、ZK、流媒体、CDN、P2P、K8S、Docker、TCP/IP、协诚、DPDK、ffmpeg。
处理被中断的系统调用
接受被称为慢系统调用,大部分网络支持功能都属于这一类。适用于慢速系统调用的基本规则是,当阻塞慢速系统调用的进程捕获到一个信号,并且相应的信号处理函数返回时,系统调用可能会返回一个EINTR错误。
从上图可以看出,三个客户终止并没有导致accept中阻塞的服务器终止;因为内核会自动重启中断的系统调用。但有些系统的标准C函数库中的signal函数不会让内核自动重启被中断的系统调用,即系统函数的signal函数中没有设置SA_RESTART标志,服务器程序会在这些系统中终止。
那么并非所有系统都会重新启动被中断的系统调用,因此要处理被中断的系统调用,请将accept的调用更改为:
if ((connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &clilen)) < 0) { if (errno == EINTR) continue; //有些系统不会重启被这中断的系统调用,所以要处理 else err_exit("accept error, server.\n"); }wait函数和waitpid函数
#include <sys/wait.h>pid_t wait(int *statloc);pid_t waitpid(pid_t pid, int *statloc, int options); //参数pid允许指定想等待的进程ID,为-1表示等待第一个终止的子进程//均返回:成功返回进程ID,出错返回0或-1//statloc指针返回子进程终止状态(一个整数)//options是附加选项,最常用的是WNOHANG,告知内核在有尚未终止的子进程在运行时不阻塞
问题:
在上述情况下,当建立信号处理函数来调用wait时,仍然会有一个死进程。问题是所有五个信号都是在执行信号处理功能之前产生的;另一方面,当客户机和服务器在一台机器上运行时,信号处理功能只执行一次,因为unix信号一般不排队。如果客户机服务器运行在不同的机器上,那么信号处理函数执行的次数是不确定的,并且不确定还剩下多少个死进程以及哪些进程将会死。
下图显示了客户机退出后,服务器创建的五个连接和四个死的子进程:
解决方案:
使用waitpid而不是wait。下面的处理函数起作用,因为waitpid是在一个循环中调用的,以获取进程终止状态。waitpid函数的参数options被指定为WNOHANG,它告诉waitpid在仍有未终止的子进程时不要阻塞。当还有子进程时,使用wait不能阻止它阻塞。
//使用Void Onsignalcatch (int信号号){ PID _ tpidint stat//PID = wait(& stat);//下面这个函数的第一个参数是-1,表示while ((PID = waitpid (-1,& stat,wno hang))> 0)printf(& # 34;子级%d已终止。\ n & # 34,PID);返回;}结果如下:
至此,客户机服务器代码完成,并添加了死进程的处理。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。