10 TCP套接字之SIGCHLD信号


上节发现了新的fork程序还有问题, 本节就来解决这个问题.

问题回顾

将两个客户端关闭, 结果呢, 服务端的两个子进程的资源没有被回收从而成为了僵尸进程了. 父进程没有正确的回收子进程的资源, 所以人家就成了僵尸.

在这里插入图片描述

其实在apue书中就讲到过, 子进程退出后父进程会收到子进程的SIGCHLD信号, 但是该信号父进程默认是忽略的. 而现在想要解决这个问题, 就必须让父进程捕捉该信号, 并作出处理.

捕捉信号

既然知道问题所在, 现在就来想办法捕捉信号. 信号捕捉可以使用signal函数也可以使用sigaction函数. 推荐使用后者比较好, 这里我就是用后者.

父进程等待子进程退出有waitwaitpid两个函数, 前者调用后只有被阻塞到有子进程退出, 后者有多个选择还可以可以设置进程不阻塞. 而服务端最好不能被阻塞, 所以我们只有选择用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");
}

信号处理中要注意两点 :

  1. 一定要死循环. UNIX信号默认不排队, 多个相同信号到来最后只会处理一次信号
  2. 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信号注意点.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值