考虑以下代码:
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),因为孩子正在处理连接。
问题是:这些假设是对的吗?
答案 0 :(得分:3)
将评论流转换为答案。
是。问题中的描述看起来是正确的,推理是合理的。
在某些时候,你的父进程应该等待已经死亡的子进程以防止僵尸的积累(但它不应该阻止直到孩子死亡)。在具有WNOHANG
参数的循环中使用waitpid()
可能是合适的,在父循环关闭new_socket_fd
的循环部分中。这可能会留下一个或多个僵尸,直到下一个传入请求。如果这是一个问题,您可以忽略SIGCHLD
(因此永远不会创建僵尸),或者您可以安排定期唤醒,在此期间父进程会检查僵尸。
快速提问 - 所以父进程何时/何时关闭socket_fd?
父级在退出循环时关闭socket_fd
,或者被告知停止侦听套接字。在所显示的代码中没有真正的规定,因此当父进程被终止(或发生fork故障)时它将被关闭。重点是听音插座可以用于许多连接 - 你不想在父母关闭它之前关闭它。
在这种情况下,因为它是一个无限循环,永远不会。服务器将始终监听
listen(socket_fd, N)
中定义的最多N个连接。
请注意,listen()
调用中的N参数是可以为侦听进程排队的未完成连接数。也就是说,通过accept()
调用尚未返回值的连接请求数。在accept()
接受连接后,它不是可以同时激活的连接数的限制。
在孩子关闭
socket_fd
之前,绑定端口是否映射到两个PID?如果有一个传入的数据包,它的队列将被放入?
传入的数据包与套接字的“打开文件描述”相关联。 (或等效的 - 与文件描述符&#39;或&#39;套接字描述符&#39;不同)。它可供父母或孩子使用,无论哪个先读取。同样,传入的连接请求在socket_fd
上排队;他们可以被父母或孩子接受。然而,这个家庭已经同意谁做了什么,所以他们不会互相帮助。
Matteo commented
对于我认为的父母。
Ajay responded
如果是这种情况,
new_socket_fd
的数据包也会发生同样的情况,因为它们都打开了。这意味着孩子在父母关闭之前无法读取数据包。这可能导致竞争条件。
这是基于一种误解。该数据包可通过文件描述符用于两个进程。当进程关闭文件描述符时,它无法再访问发送到连接的信息(或者当然在该连接上发送数据)。在那之前,除非参与者处理同意哪个读取数据以及哪个人监听连接,否则看到什么是彩票?
Matteo responded
但是文件描述符不应该干扰父母和孩子;这就是为什么在孩子那边关闭
socket_fd
并不能阻止父母听。
babon commented
同意。但我认为你应该在while循环后关闭
socket_fd
。如果明天你将循环更改为某些条件中断,则存在无缘无故地保持打开套接字的风险。
这是一个很好的做法,但循环不会退出(它是一个while (1)
循环,而失败模式会在循环外执行return
- 它可能会先关闭套接字这样做return
)。如果程序退出,那么系统将关闭套接字,因此它并不重要,因为关闭所打开的内容是很好的管理。
Ajay notes
父和子中的文件描述符都不同。因此,关闭一个不应该影响另一个。但是这两个副本都有相同的(src-ip,src-port,dest-ip,dest-port)4-tuple,那么带有这样一个头的数据包会在哪里出现?
描述符不同,但它们引用的套接字连接是相同的。该数据包可供任何读取它的进程使用 - 父或子。
Matteo responded
在我的示例
accept_client
中为客户端创建了sockaddr
结构,因此4元组会转到孩子的new_socket_fd
。
这不是很准确。首先,在有孩子之前调用accept_client()
;当该函数完成时,new_socket_fd
在父(仅)中。其次,在fork()
之后,两个进程都可以访问new_socket_fd
,并且可以读取客户端进程发送的数据。但是,该程序的设计是为了让服务器在new_socket_fd
处理传入连接时,监听更多的连接请求,这是一种合理的分工。
请注意,允许父进程处理请求并且子进程继续侦听是允许的。但是,这与惯例相反。这意味着守护神&#39;进程侦听在每个连接请求上更改PID,使得很难确定哪个进程当前正在侦听套接字。代码中使用的传统方法是守护进程在很长一段时间内保持不变,因此记录PID以便以后进行过程控制是合理的(当守护进程不再需要时终止守护进程)。