这是在 fork 上关闭套接字描述符的正确方法吗? [英] Is this the correct way to close socket descriptors on fork?

查看:27
本文介绍了这是在 fork 上关闭套接字描述符的正确方法吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑这个代码:

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() 时,它的地址空间是重复的但不共享,所以如果我更改变量或关闭子进程的文件描述符,它不会影响父进程.

From what I understand, when a process calls fork(), its address space is duplicated but not shared, so if I change variables or close file descriptors from child's process, it doesn't affect the parent.

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

That said, after the server has accepted a new connection (thus creating new_socket_fd) it forks itself, and the child closes socket_fd (1) because it's not required since the parent is still listening at its socket_fd.

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

The child processes the requests and then closes its new_socket_fd (2) and exits.

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

While the child is doing all of these, the parent process has already closed new_socket_fd (3) since the connection is being handled by the child.

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

The question is: are these assumptions right?

推荐答案

将评论流转换为答案.

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

Yes. The description in the question looks correct and the reasoning is sound.

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

At some point, your parent process should wait for child processes that have died to prevent the accumulation of zombies (but it shouldn't block until a child dies). Using waitpid() in a loop with the WNOHANG argument is probably appropriate, in the portion of the loop where the parent closes the new_socket_fd. This could leave one or more zombies around until the next incoming request is made. If that's a problem, you could ignore SIGCHLD (so zombies are never created), or you could arrange a periodic wake-up during which the parent process checks for zombies.

babon 询问

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

Quick question - so when / where does the parent process close socket_fd?

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

The parent closes the socket_fd when it exits the loop or has been told to stop listening on the socket. There's no real provision for that in the code shown, so it will be closed when the parent process is killed (or a fork failure occurs). The whole point is that the listening socket can be used for many connections — you don't want to close it in the parent until you've finished listening.

Matteo 注意到

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

In this case since it's an endless loop, never. The server will always be listening for up to N connections defined in listen(socket_fd, N).

请注意,listen() 调用中的 N 参数是可以排队等待侦听进程的未完成连接数.也就是说,尚未通过 accept() 调用返回值的连接请求数.accept() 接受连接后可以同时激活的连接数没有限制.

Note that the N parameter in the listen() call is the number of outstanding connections that can be queued for the listening process. That is, the number of connection requests that have not yet returned a value via the accept() call. It is not a limit on the number of connections that can be active concurrently after the accept() has accepted the connection.

Ajay Brahmakshatriya 询问

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

Before the child closes the socket_fd, is the bound port mapped to both the PIDs? If there is a incoming packet, whose queue will it be put into?

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

The incoming packet is associated with the socket's 'open file description' (or equivalent — as distinct from 'file descriptor' or 'socket descriptor'). It is available to either parent or child, whichever reads it first. Similarly, incoming connection requests are queued on the socket_fd; they could be accepted by either parent or child. However, the family has agreed who does what so they don't get in each other's way.

Matteo 评论

我假设是给父母的.

Ajay 回复了

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

If that is the case, the same should also happen for the packets for the new_socket_fd since both have it open. This means that the child won't be able to read the packets until the parent closes it. This can lead to race conditions.

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

This is based on a misunderstanding. The packet is available to both processes via the file descriptor. When a process closes the file descriptor, it can no longer access the information sent to connection (or send data on that connection, of course). Until then, it is a lottery who sees what unless the participant processes agree on which one reads the data and which one listens for connections

Matteo 回复了

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

But file descriptor shouldn't interfere between parent and child; that's why closing socket_fd on the child side doesn't stop the parent from listening.

babon 评论

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

Agreed. But I think you should close socket_fd after the while loop. In case tomorrow you change the loop to break for some condition you run the risk of keeping an open socket for no reason.

这是一个很好的做法,但循环不会退出(它是一个while (1) 循环,并且失败模式在循环之外执行return— 它可以在执行该操作之前关闭套接字 return).如果程序退出,那么系统会关闭套接字,所以这并不重要,因为关闭您打开的内容是很好的内务.

This is good practice, but the loop doesn't exit (it is a while (1) loop, and the failure mode does a return out of the loop — it could close the socket before doing that return). And if the program exits, then the system closes the socket, so it isn't crucial, for all it is good housekeeping to close what you open.

Ajay 笔记

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

Both the file descriptors in the parent and child are different. So closing one should not affect the other. But both the copies have the same (src-ip, src-port, dest-ip, dest-port) 4-tuple and so where would a packet with such a header go?

描述符不同,但它们所指的socket连接是一样的.数据包可供任何读取它的进程使用——父进程或子进程.

The descriptors are different, but the socket connection they refer to is the same. The packet is available to any process that reads it — parent or child.

Matteo 回复了

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

In my example accept_client creates the sockaddr struct for the client, so the 4-tuple goes to child's new_socket_fd.

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

This isn't quite accurate. First, accept_client() is called before there is a child; the new_socket_fd is in the parent (only) when that function completes. Second, after the fork(), both processes have access to the new_socket_fd, and either could read the data sent by the client process. However, the program is designed so that the server goes back to listening for more connection requests while the child processes the incoming connection on new_socket_fd — a sensible division of labour.

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

Note that it would be permissible to have the parent process the request and the child continue listening. However, this is contrary to convention. It would mean that the 'daemon' process listening changes PID on each connection request, making it hard to determine which process is currently listening on the socket. The conventional approach used in the code is that the daemon process remains the same over long periods of time, making it sensible to record the PID for later process control (killing the daemon when it isn't needed any more).

这篇关于这是在 fork 上关闭套接字描述符的正确方法吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆