这是在fork上关闭套接字描述符的正确方法吗?

时间:2017-07-19 12:17:15

标签: c sockets client-server fork tcpclient

考虑以下代码:

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),因为孩子正在处理连接。

问题是:这些假设是对的吗?

1 个答案:

答案 0 :(得分:3)

将评论流转换为答案。

TL; DR

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

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

讨论

babon asked

  

快速提问 - 所以父进程何时/何时关闭socket_fd?

父级在退出循环时关闭socket_fd,或者被告知停止侦听套接字。在所显示的代码中没有真正的规定,因此当父进程被终止(或发生fork故障)时它将被关闭。重点是听音插座可以用于许多连接 - 你不想在父母关闭它之前关闭它。

Matteo noted

  

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

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

Ajay Brahmakshatriya asked

  

在孩子关闭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以便以后进行过程控制是合理的(当守护进程不再需要时终止守护进程)。