C/S模型下Server 中fork()的健壮性

C/S模型下Server 中fork()的健壮性

C/S模型下Server 下编程非常依赖fork()子进程去处理具体的业务逻辑。 每当accept()接收到一个TCP连接时,主服务器进程就fork一个子服务器进程。子服务器进程调用相应的函数,通过client_fd(连接套接字)对客户端发来的网络请求进程处理;由于客户端的请求已被子服务进程处理,那么主服务器进程就什么也不做,通过sockfd(监听套接字)继续循环等待新的网络请求。


..........
while (1) {
    sin_size = sizeof(struct sockaddr_in);
    if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
        my_error("accept", errno, __LINE__);
        continue;
    }
 
    if ((pid = fork()) == 0) {
        close(sockfd);
        process_client_request(client_fd);
        close(client_fd);
        exit(0);
    } else if (pid > 0)
        close(client_fd);
    else
        my_error("fork", errno, __LINE__);
}

每个文件都有一个引用计数,该引用计数表示当前系统内的所有进程打开该文件描述符的个数。套接字是一种特殊的文件,当然也有引用计数。 当fork执行后,由于子进程复制了父进程的资源,所以子进程也拥有这两个套接字描述符,则此时sockfd和client_fd的引用计数都为2。只有当子进程处理完客户请求时,client_fd的引用计数才由于close函数而变为0。 但是这里存在一个严重的问题:如果客户端意外退出,就会导致server子进程成为僵尸进程。我们设想如果server非常繁忙,就会导致system出现大量的zombie进程!system会应该耗尽系统资源而宕机! 这是因为

当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止。 子进程退出时,内核将子进程置为僵尸状态,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。 进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait调用使用。 所以我们要处理zombie进程!

两种方式:


void sig_zchild(int signo)
{
    pid_t pid;
    int stat;
 
    pid = wait(&stat);
        printf("child %d terminated\n", pid);
    return;
}

修改服务器程序,在accept函数调用之前调用signal函数:


    if(listen(sockfd, BACKLOG) == -1) {
 
        printf("listen error!\n");
        exit(1);
    }
 
    if (signal(SIGCHLD, sig_zchild) == SIG_ERR) {
        printf("signal error!\n");
        exit(1);
    }
 
    while (1) {
 
        sin_size = sizeof(struct sockaddr_in);
        if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr,
&sin_size)) == -1) {
 
            printf("accept error!\n");
            continue;
        }
        …… ……
    }//while

但是这个程序仍存在一个问题:当多个子进程同时退出时,会导致父进程无法同时处理SIGCHILD信号,导致有部分子进程zombie。


void sig_zchild(int signo)
{
 pid_t pid;
 int stat;
 
 while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
 printf("child %d terminated\n", pid);
 
 return;
}

使用while可以等待SIGCHILD信号,直到处理完成! 信号在内核中也是存放在队列中!

参考:http://www.cnblogs.com/mickole/p/3187770.html