【问题标题】:Is this the correct way to close socket descriptors on fork?这是在 fork 上关闭套接字描述符的正确方法吗?
【发布时间】:2017-12-24 17:04:41
【问题描述】:

考虑这段代码:

socket_fd = start_server(port);

while (1){

    new_socket_fd = accept_client(socket_fd);

    int pid = fork();

    if (pid == 0){

        //I am the child server process

        close(socket_fd);      <------(1)

        do_stuff_with_client(new_socket_fd, buffer);

        close(new_socket_fd);       <------(2)

        exit(0);

    } else if (pid > 0){

        //I am the parent server process

        close(new_socket_fd);      <------(3)

    } else {

        fprintf(stderr, "Fork error\n");
        return 1;
    }
}

据我了解,当一个进程调用 fork() 时,它的地址空间是重复的,但不是共享的,所以如果我从子进程更改变量或关闭文件描述符,它不会影响父进程。

也就是说,在服务器接受新连接(从而创建new_socket_fd)后,它会分叉自己,并且子进程关闭socket_fd (1),因为它不是必需的,因为父进程仍在其socket_fd 上监听.

子进程处理请求,然后关闭其new_socket_fd (2) 并退出。

当子进程执行所有这些操作时,父进程已经关闭new_socket_fd (3),因为连接正在由子进程处理。

问题是:这些假设是否正确?

【问题讨论】:

  • 是的。这看起来是正确的,而且推理是合理的。
  • @JonathanLeffler 嗯...快速提问 - 那么父进程何时/在哪里关闭socket_fd
  • @JonathanLeffler 在孩子关闭 socket_fd 之前绑定端口是否映射到两个 PID?如果有传入的数据包,它将被放入谁的队列?
  • 请注意,listen() 调用中的N 参数是可以排队等待侦听进程的未完成连接数。即,尚未通过accept() 调用返回值的连接请求数。在accept()接受连接后,对可以同时活动的连接数没有限制。
  • @AjayBrahmakshatriya:在孩子关闭 socket_fd 之前,绑定端口是否映射到两个 PID?如果有一个传入的数据包,它将被放入谁的队列中? 传入的数据包与套接字的“打开文件描述”(或等效的 - 与“文件描述符”或“套接字描述符”不同)相关联。它可供父母或孩子使用,以先阅读者为准。同样,传入的连接请求在socket_fd 上排队;他们可以被父母或孩子接受。但是,家人已经同意谁做什么,所以他们不会妨碍彼此。

标签: c sockets client-server fork tcpclient


【解决方案1】:

将评论流转化为答案。

TL;博士

是的。问题中的描述看起来正确,推理合理。

在某些时候,您的父进程应该等待已经死亡的子进程以防止僵尸的积累(但它不应该阻塞,直到子进程死亡)。在带有WNOHANG 参数的循环中使用waitpid() 可能是合适的,在循环的父项关闭new_socket_fd 的部分中。这可能会留下一个或多个僵尸,直到发出下一个传入请求。如果这是个问题,您可以忽略 SIGCHLD(因此永远不会创建僵尸),或者您可以安排定期唤醒,在此期间父进程检查僵尸。

讨论

babonasked

快速提问 - 那么父进程何时/何地关闭 socket_fd?

父级在退出循环或被告知停止侦听套接字时关闭socket_fd。在显示的代码中没有真正的规定,因此当父进程被杀死(或发生分叉失败)时它将关闭。重点是监听套接字可用于许多连接——在完成监听之前,您不想在父级中关闭它。

Matteonoted

在这种情况下,因为它是一个无限循环,所以永远不会。服务器将始终监听listen(socket_fd, N) 中定义的最多 N 个连接。

请注意,listen() 调用中的 N 参数是可以排队等待侦听进程的未完成连接数。即,尚未通过accept() 调用返回值的连接请求数。在accept() 接受连接后,可以同时处于活动状态的连接数不受限制。

Ajay Brahmakshatriyaasked

在孩子关闭socket_fd之前,绑定端口是否映射到两个PID?如果有传入的数据包,会放入谁的队列?

传入的数据包与套接字的“打开文件描述”(或等效的 - 与“文件描述符”或“套接字描述符”不同)相关联。它可供父母或孩子使用,以先阅读者为准。同样,传入的连接请求在socket_fd 上排队;他们可以被父母或孩子接受。但是,家人已经同意谁做什么,这样他们就不会妨碍彼此。

马特奥commented

我假设是父母的。

阿杰responded

如果是这种情况,new_socket_fd 的数据包也应该发生同样的情况,因为两者都打开了它。这意味着在父级关闭它之前,子级将无法读取数据包。这可能会导致竞争条件。

这是基于误解。该数据包可通过文件描述符提供给两个进程。当一个进程关闭文件描述符时,它不能再访问发送到连接的信息(当然也不能在该连接上发送数据)。在那之前,除非参与者进程就哪个读取数据和哪个侦听连接达成一致,否则谁知道什么是彩票。

马特奥responded

但是文件描述符不应该在父子之间产生干扰;这就是为什么在子端关闭 socket_fd 并不会阻止父级收听。

狒狒commented

同意。但我认为你应该在 while 循环之后关闭 socket_fd。万一明天您更改循环以因某种情况中断,您将冒着无缘无故保持打开套接字的风险。

这是一个很好的做法,但循环不会退出(这是一个while (1) 循环,故障模式会在循环外执行return - 它可以在这样做之前关闭套接字return) .如果程序退出,那么系统会关闭套接字,所以这并不重要,因为关闭你打开的东西是一件好事。

阿杰notes

父子文件描述符不同。所以关闭一个不应该影响另一个。但是两个副本都有相同的 (src-ip, src-port, dest-ip, dest-port) 4 元组,那么带有这样一个标头的数据包会去哪里呢?

描述符不同,但它们引用的套接字连接是相同的。该数据包可供任何读取它的进程使用——父进程或子进程。

马特奥responded

在我的示例中,accept_client 为客户端创建了 sockaddr 结构,因此 4 元组转到孩子的 new_socket_fd

这不太准确。首先,accept_client() 在有孩子之前被调用; new_socket_fd 在该功能完成时(仅)在父级中。其次,在fork()之后,两个进程都可以访问new_socket_fd,并且可以读取客户端进程发送的数据。但是,该程序的设计目的是让服务器在子进程处理 new_socket_fd 上的传入连接时返回侦听更多连接请求——这是一种明智的分工。

请注意,允许父进程处理请求,而子进程继续侦听。然而,这有悖于惯例。这意味着监听的“守护进程”进程会在每个连接请求上更改 PID,从而难以确定当前正在监听套接字的进程。代码中使用的常规方法是守护进程在很长一段时间内保持不变,因此可以明智地记录 PID 以供以后的进程控制(当不再需要守护进程时将其杀死)。

【讨论】:

  • "数据包通过文件描述符对两个进程都可用。当进程关闭文件描述符时,它不能再访问发送到连接的信息(当然也不能在该连接上发送数据) . 在那之前,除非参与者进程就哪个读取数据和哪个侦听连接达成一致,否则就是彩票谁看到了什么“这是我认为的主要观点,关于该主题的其他答案也指出了僵尸的问题,所以稍后我会看看。感谢您的重述。
猜你喜欢
  • 2014-04-17
  • 2015-03-17
  • 1970-01-01
  • 1970-01-01
  • 2012-10-01
  • 1970-01-01
  • 2018-06-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多