Chinaunix首页 | 论坛 | 博客
  • 博客访问: 75945
  • 博文数量: 15
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 265
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-21 18:15
文章分类

全部博文(15)

文章存档

2009年(8)

2008年(7)

我的朋友

分类: LINUX

2009-02-17 20:39:08

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)) < 0) {

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)) < 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)) < 0) {

        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_INTERRUPT
11         act.sa_flags |= SA_INTERRUPT;     /* SunOS 4.x */
12 #endif
13     } else {
14 #ifdef  SA_RESTART
15         act.sa_flags |= SA_RESTART; /* SVR4, 4.4BSD */
16 #endif
17     }
18     if (sigaction (signo, &act, &oact) < 0)
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)) < 0) {
             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)) < 0) {

             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

阅读(2377) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

aobai2009-07-02 21:54:24

你确定你的程序显示正常吗? 我在虚拟机上面实现这段代码的时侯信号处理函数sig_chld 显示不正常。。