首页 文章

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

提问于
浏览
1

考虑以下代码:

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 回答

  • 3

    将评论流转换为答案 .

    TL; DR

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

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

    讨论

    babon asked

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

    当父级退出循环或被告知停止侦听套接字时,父级会关闭 socket_fd . 有'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'想要在父母中关闭它,直到你听完为止 .

    Matteo noted

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

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

    Ajay Brahmakshatriya asked

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

    传入的数据包与套接字's '打开文件描述符' (or equivalent — as distinct from '文件描述符' or '套接字描述符')相关联 . 它可供父母或孩子使用,无论哪个先读取 . 同样,传入的连接请求在 socket_fd 上排队;他们可以被父母或孩子接受 . 然而,这个家庭已经同意谁做了什么,所以他们不会这样做 .

    Matteo commented

    对于我认为的父母 .

    Ajay responded

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

    这是基于一种误解 . 该数据包可通过文件描述符用于两个进程 . 当进程关闭文件描述符时,它无法再访问发送到连接的信息(或者当然在该连接上发送数据) . 在此之前,除非参与者处理同意哪个读取数据以及哪个人监听连接,否则看到什么是乐透

    Matteo responded

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

    babon commented

    同意 . 但我认为你应该在while循环后关闭socket_fd . 如果明天您将某个条件更改为中断,则可能会无缘无故地保持打开套接字 .

    这是一个很好的做法,但循环不会退出(它是一个 while (1) 循环,并且失败模式在循环之外执行 return - 它可以在执行_2450158之前关闭套接字) . 如果程序退出,那么系统会关闭套接字,所以它并不重要,因为所有这些都是很好的管理来关闭你打开的东西 .

    Ajay notes

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

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

    Matteo responded

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

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

    请注意,允许父进程处理请求并且子进程继续侦听是允许的 . 但是,这与惯例相反 . 这意味着'守护进程'进程侦听会在每个连接请求上更改PID,这使得很难确定哪个进程当前正在侦听套接字 . 代码中使用的传统方法是守护进程在很长一段时间内保持不变,因此记录PID以便以后进行过程控制(在不再需要时杀死守护进程)是明智的 .

相关问题