【问题标题】:wait function in the book of unix network programming?Unix网络编程书中的等待函数?
【发布时间】:2014-05-22 09:30:55
【问题描述】:

在 Stevens 等人的书Unix Network Programming 的第 5 章中,有一个服务器程序和一个客户端程序,如下所示:

服务器

mysignal(SIGCHLD, sig_child);
for(;;)
{
    connfd = accept(listenfd, (struct sockaddr *)&ca, &ca_len);

    pid = fork();
    if(pid == 0)
    {
        //sleep(60);
        close(listenfd);
        str_echo(connfd);
        close(connfd);
        exit(0);
    }
    close(connfd);
}

函数 sig_child 用于处理信号 SIGCHLD;代码如下:

void sig_child(int signo)
{
    pid_t pid;
    int stat;
    static i = 1;
    i++;
    while(1)
    {
        pid = wait(&stat);
        if(pid > 0)
        {
            printf("ith: %d, child %d terminated\n", i, pid);
        }
        else
        {
            break;
        }
    }   
    //pid = wait(&stat);
    return;
}

客户

for(i = 0 ; i < 5; i++)
{
    sockfd[i] = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd[i] < 0)
    {
        perror("create error");
        exit(-1);
    }

    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(5900);

    if(inet_pton(AF_INET, argv[1], &sa.sin_addr) != 1)
        {
                perror("inet_pton error");
                exit(-1);
        }
    connect(sockfd[i], (struct sockaddr *)&sa, sizeof(sa));
}
str_cli(sockfd[0], stdin);
exit(0);

在客户端源码中可以看到,程序会与服务器端建立5个连接,但程序中只使用了1个连接; str_cli 完成后,exit(0) 被调用。并且所有连接都应该关闭,然后服务器中的五个子进程将退出,并将SIGCHLD发送给父进程,父进程使用函数sig_child处理SIGCHLD。然后while 循环将确认所有子进程将被父进程正确等待。我对该程序进行了几次测试;效果很好,所有的孩子都会被清理干净。

但在书中,作者写道“wait 不能正常工作,因为函数 wait 可能在所有子进程退出之前就被阻塞了”。那么书上的说法对吗?如果是对的,请您详细解释一下。 (PS:我认为while 语句中的wait 可以正确处理所有子进程的退出。)

【问题讨论】:

  • 这到底是哪本书?作者?标题?出版商?年?版?页面?
  • @EJP unpbook.com,第三版,第5章。我看的是中文版的,所以我没有找到英文版的确切页面。
  • 那段号呢?章节?最近的前一个标题?你必须提供一些线索。
  • static i = 1; 是 C89 或准标准代码; C99 或更高版本需要一个类型(之前推断为int)——初始化器可能应该是0 而不是1。请注意,printf() 不能在信号处理函数中正式安全地调用。 sig_child() 中的循环有点洛可可;您可能应该使用while ((pid = wait(&amp;status)) &gt; 0) { … }。这从根本上简化了循环体(其中仅包含 printf(),假设您继续与死亡调情),将 12 行减少到 4 行,甚至只有 2 行。
  • 如果您在sig_child() 中有一个循环,并且您有(例如)3 个子进程并且其中 1 个终止,则信号处理程序中的循环将不会终止,直到所有 3 个子进程都死了。您需要使用 waitpid()W_NOHANG 而不是 wait()

标签: c unix


【解决方案1】:

问题不在于wait,而在于信号传递。书中的sig_chld函数没有while循环,它只等待一个孩子

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

当客户端退出时,所有连接都会关闭,所有子节点最终都会终止。现在,第一个SIGCHLD 信号被传递,进入信号处理程序后,信号被阻塞。任何进一步的信号都不会排队,因此会丢失,从而导致服务器中的僵尸子节点。

您可以通过将wait 包装在某个循环中来解决此问题,就像您所做的那样。另一种解决方案是显式忽略SIGCHLD,这在您不需要孩子的退出状态时有效。


虽然wait 在一个循环中最终等待所有孩子,但它有一个缺点,如果仍有孩子在运行,wait 会阻塞。这意味着进程会一直停留在信号处理程序中,直到所有子进程都被终止。

书中的解决方案是在循环中使用waitpid和选项WNOHANG

while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
    printf("child %d terminated\n", pid);

这个循环等待所有终止的孩子,但尽快退出,即使有正在运行的孩子。


要重现挂在信号处理程序中的服务器,您必须执行以下操作

  • 启动服务器
  • 启动第一个客户端
  • 启动第二个客户端
  • 关闭其中一个客户端
  • 启动第三个客户端
  • 在第三个客户端中输入文本
    您不会收到回复

【讨论】:

  • 在第 5.10 章中,它确实有一个 while 循环,但在书中,它说循环中的 wait 无法完成正确的工作,而循环中的 waitpid 可以正常工作。
  • 感谢您的更新,我尝试在循环中使用wait,并从两个客户端连接到服务器,但服务器端进程没有卡住。
  • 我尝试循环使用wait,我使用两个客户端(每个建立5个连接)连接服务器,客户端1会先关闭一个连接,这会导致一个服务器子进程退出,然后我用另一个客户端2连接服务器,可以成功建立连接,服务器进程没有卡住(有四个服务器进程还在连接客户端1,但没有卡在等待中)
  • 谢谢你的回答,真的很有帮助。
【解决方案2】:

关于作者对那句话的意思,我的两分钱。

SIGCHLD 在以下三个条件下引发

1) 子退出 2) 孩子被打断 3) 孩子继续。

如果 2 和 3 等待将被阻止,因为孩子还没有退出。

【讨论】:

  • 但是wait的block不会影响所有进程都会被父进程清理的结果(这正是我困惑的地方),因为wait是以while的方式调用的。
  • wait 如果失败则不会再次调用。 else 分支有一个中断。
  • 如果子进程没有退出,调用“wait”函数会阻塞父进程。考虑在第一个孩子分叉后,该孩子被挂起并因此父母在 SIGCHLD 处理程序中被阻塞的情况。现在它不能再接受任何连接,直到第一个孩子退出。这可能是不可取的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-09
  • 1970-01-01
  • 1970-01-01
  • 2018-02-28
  • 2015-09-12
相关资源
最近更新 更多