免费注册 查看新帖 |

Chinaunix

广告
  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1199 | 回复: 0
打印 上一主题 下一主题

服务端子进程终止时的信号处理问题 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-02-17 20:39 |只看该作者 |倒序浏览

Unix网络编程中服务端子进程终止时的信号处理问题。
在《unix网络编程》中有这样一个例子:
服务端:
#include
#include
#include
#include
#include
#include
#include

#define SERV_PORT   12345
#define LISTENQ     1024

int main(int argc, char ** argv) {
int                 listenfd, connfd;
pid_t               childpid;
socklen_t           clilen;
struct sockaddr_in  cliaddr, servaddr;

if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("socket error!\n");
exit(1);
}

memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);

if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {
printf("bind error!\n");
exit(1);
} else {
printf("bind successfully!\n");
}

if (listen(listenfd, LISTENQ) != 0) {
printf("listen error!\n");
exit(1);
} else {
printf("listen successfully!\n");
}

for(;;) {
clilen = sizeof(cliaddr);
if ( (connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen))
printf("accept error!\n");
}

if ( (childpid = fork()) == 0) {
close(listenfd);
str_echo(connfd);
exit(0);
}

close(connfd);
}
}

客户端:
#include
#include
#include
#include
#include
#include

int main(int argc, char ** argv) {
    int                 sockfd;
    struct sockaddr_in  servaddr;

    if (argc != 2) {
        printf("error!\n");
        exit(1);
    }

    if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0))
        printf("socket error!\n");
        exit(1);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(12345);
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))
        printf("connect error!\n");
        exit(1);
    }

    str_cli(stdin, sockfd);

    exit(0);
}
这个程序有个问题:fork子进程后没有捕获SIGCHLD信号。
       在服务器子进程终止的时候,会给父进程发送一个SIGCHLD信号。但是我们在代码中并没有对其进行处理,则内核对该信号的缺省操作是被忽略。于是子进程遍进入僵死状态,我们可以用一个小程序来验证一下:(fork.c)
#include

int main(int argc, char ** argv) {

    int pid;
   
    if ( (pid = fork()) == 0) {
        // do nothing ...
    } else {
        // sleep
        sleep(10000);
    }
}
编译:
-bash-3.2$ gcc -o forktest fork.c
运行程序之前用ps查看一下:
-bash-3.2$ ps
  PID TTY          TIME CMD
4603 pts/29   00:00:00 ps
13968 pts/29   00:00:01 bash
运行程序:
-bash-3.2$ ./forktest &
[1] 4869
然后用ps查看一下:
-bash-3.2$ ps
  PID TTY          TIME CMD
4869 pts/29   00:00:00 forktest
4870 pts/29   00:00:00 forktest
4873 pts/29   00:00:00 ps
13968 pts/29   00:00:01 bash

可见子进程进入僵死状态,我们可以看的更加清楚一些:
-bash-3.2$ ps -t pts/29 -o pid,ppid,tty,stat,args,wchan
  PID  PPID TT       STAT COMMAND                     WCHAN
4869 13968 pts/29   S    ./forktest                  hrtimer_nanosleep
4870  4869 pts/29   Z    [forktest]         exit
4882 13968 pts/29   R+   ps -t pts/29 -o pid,ppid,tt -
13968 13967 pts/29   Ss   -bash                       wait

状态Z就代表僵死状态。
《unix网络编程》中的信号处理函数是自己写的Signal函数,他对sigaction函数进行了包装:
1 #include    "unp.h"  2 Sigfunc * 3 Signal (int signo, Sigfunc *func) 4 { 5     struct sigaction act, oact;  6     act.sa_handler = func; 7     sigemptyset (&act.sa_mask); 8     act.sa_flags = 0; 9     if (signo == SIGALRM) {10 #ifdef  SA_INTERRUPT11         act.sa_flags |= SA_INTERRUPT;     /* SunOS 4.x */12 #endif13     } else {14 #ifdef  SA_RESTART15         act.sa_flags |= SA_RESTART; /* SVR4, 4.4BSD */16 #endif17     }18     if (sigaction (signo, &act, &oact) 19         return (SIG_ERR);20     return (oact.sa_handler);21 }

注意第15行这里的SA_RESTART标志,如果设置,有相应信号中断的系统调用将有内核自动重启,但是并非所有系统都支持这个标志,所以要有一个通用的解决办法。
在本例中父进程阻塞与accept系统调用,子进程发送的SIGCHLD信号在此时捕获,则在accept中可能会返回一个EINTR错误(被中断的系统调用)。有些内核自动重启某些被中断的系统调用,不过为了移植性,当我们编写程序时,我们必须对EINTR错误有所准备。
       为了处理中断的accept函数,我们将代码改写如下:
  for ( ; ; ) {         clilen = sizeof (cliaddr);         if ( (connfd = accept (listenfd, (SA *) &cliaddr, &clilen))              if (errno == EINTR)                 continue;         /* back to for () */             else                 err_sys ("accept error");        }
这段代码会就是自己重启被中断的系统调用。

还有一个问题需要注意的是在写信号处理函数的时候获得已终止子进程进程好的方法要用waitpid而不是wait,因为当有多个子进程在信号处理函数前终止,那么wait只会捕获到第一个终止的子进程。正确的做法是使用waitpid,在一个循环内调用waitpid以获得所有已终止子进程的状态,而且必须制定WNOHANG选项,意思是在有尚未终止的子进程在运行是不要阻塞。
完整的代码如下:
信号处理函数:
1 #include    "unp.h"  2 void 3 sig_chld(int signo) 4 { 5     pid_t    pid; 6     int      stat;  7     while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) 8         printf("child %d terminated\n", pid); 9     return;10 }
服务器端代码:
#include
#include
#include
#include
#include
#include
#include

#define SERV_PORT   12345
#define LISTENQ     1024

int main(int argc, char ** argv) {
    int                 listenfd, connfd;
    pid_t               childpid;
    socklen_t           clilen;
    struct sockaddr_in  cliaddr, servaddr;

    if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("socket error!\n");
        exit(1);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {
        printf("bind error!\n");
        exit(1);
    } else {
        printf("bind successfully!\n");
    }

    if (listen(listenfd, LISTENQ) != 0) {
        printf("listen error!\n");
        exit(1);
    } else {
        printf("listen successfully!\n");
    }

    for(;;) {
        clilen = sizeof(cliaddr);
       if ( (connfd = accept (listenfd, (SA *) &cliaddr, &clilen))
             if (errno == EINTR)
                 continue;          /* back to for() */
             else
                 pringf("accept error\n");
         }


        if ( (childpid = fork()) == 0) {
            close(listenfd);
            str_echo(connfd);
            exit(0);
        }
        
        close(connfd);
    }
}
总结:
在进行并发程序编写是要注意下面问题:
1.       当fork子进程时,必须对SIGCHLD信号进行处理
2.       当捕获信号时,必须处理被中断的系统调用
3.       信号处理函数必须用waitpid而不是wait


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/79926/showart_1831856.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP