C语言 这是在分叉上关闭套接字描述符的正确方法吗?



请考虑以下代码:

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 errorn");
return 1;
}
}

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

也就是说,在服务器接受新连接(从而创建new_socket_fd)后,它会自行分叉,子服务器关闭socket_fd(1),因为它不是必需的,因为父级仍在侦听其socket_fd

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

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

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

将评论流转换为答案。

TL;博士

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

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

讨论

巴邦问道

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

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

马泰奥指出

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

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

阿杰婆罗门夏特里问道。

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

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

马泰奥评论道。

我假设是父母的。

阿杰回应

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

这是基于误解。 数据包可通过文件描述符提供给两个进程。 当进程关闭文件描述符时,它无法再访问发送到连接的信息(当然,也无法在该连接上发送数据)。 在那之前,除非参与者处理同意哪个读取数据以及哪个侦听连接,否则这是一个彩票,除非参与者处理

同意马泰奥回应

但是文件描述符不应该干扰父端和子端;这就是为什么在子端关闭socket_fd不会阻止父端侦听。

巴邦评论道

同意。但我认为你应该在 while 循环后关闭socket_fd。如果明天您将循环更改为某些情况而中断,您将冒着无缘无故保持打开插座的风险。

这是一个很好的做法,但循环不会退出(这是一个while (1)循环,并且故障模式会return出循环——它可以在执行该return之前关闭套接字)。 如果程序退出,则系统会关闭套接字,因此这并不重要,因为关闭您打开的内容是很好的内务管理。

阿杰笔记

父项和子项中的文件描述符都不同。因此,关闭一个不应影响另一个。但是两个副本都有相同的(src-ip,src-port,dest-ip,dest-port)4元组,那么具有这种标头的数据包会去哪里?

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

马泰奥回应

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

这不太准确。 首先,accept_client()在有孩子之前就被召唤;当该函数完成时,new_socket_fd位于父项中(仅)。 其次,在fork()之后,两个进程都可以访问new_socket_fd,并且任何一个进程都可以读取客户端进程发送的数据。 但是,该程序的设计使服务器返回侦听更多连接请求,而子节点在new_socket_fd上处理传入的连接 - 这是一种合理的分工。

请注意,允许父级处理请求,子级继续侦听。 但是,这违背了惯例。 这意味着侦听"守护进程会更改每个连接请求上的 PID,从而难以确定当前正在侦听套接字的进程。 代码中使用的传统方法是守护进程在很长一段时间内保持不变,因此记录 PID 以供以后的进程控制(在不再需要守护进程时杀死守护进程)是明智的。

最新更新