简体   繁体   中英

Is this the correct way to close socket descriptors on fork?

Consider this code:

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;
    }
}

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.

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 .

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

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?

Converting the comment stream into an answer.

TL; DR

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

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.

Discussion

babon asked

Quick question - so when / where does the parent process close 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 noted

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) .

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 asked

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?

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 commented

To the parent's one I assume.

Ajay responded

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 responded

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 commented

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.

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 notes

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?

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 responded

In my example accept_client creates the sockaddr struct for the client, so the 4-tuple goes to child's 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.

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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM