Linux/UNIX系统上常见的服务器套接字模式是侦听套接字,接受连接,然后使用fork()
处理连接。
所以,看起来在你accept()
和fork()
之后,一旦你进入子进程,你就继承了父进程的监听文件描述符,我已经读到了,在这一点上,你需要从子进程中关闭监听套接字文件描述符。
我的问题是,为什么?这仅仅是为了减少侦听套接字的引用计数吗?还是为了使子进程本身不会被操作系统用作路由传入连接的候选进程?如果是后者,我有点困惑,原因有两个:
(A)是什么告诉操作系统某个进程可以接受某个文件描述符上的连接?是进程调用了accept()
的事实吗?还是进程调用了listen()
的事实?
(B)如果进程调用了listen()
,这里不是有一个争用条件吗?如果发生这种情况该怎么办:
1.父进程侦听套接字S。
1.传入连接转到父进程。
1.父进程派生子进程,子进程具有套接字S的副本
1.* 在 * 子进程能够调用close(S)
之前,* 第二个 * 传入连接转到子进程。
1.子进程从不调用accept()
(因为它不应该调用),因此传入连接被丢弃
是什么阻止了上述情况的发生?更一般地说,为什么一个子进程要关闭侦听套接字?
5条答案
按热度按时间3z6pesqy1#
Linux将挂起的连接排队,从父进程或子进程调用
accept
将轮询该队列。不关闭子进程中的套接字是一种资源泄漏,但除此之外没有太大影响,父进程仍然会获取所有传入的连接,因为它是唯一调用
accept
的进程,但如果父进程退出,套接字仍然存在,因为它在子进程上是打开的,即使子进程从未使用过它。vc6uscn92#
传入的连接将被“传递”到任何调用
accept()
的进程。在关闭文件描述符之前分叉之后,您可以在两个进程中接受该连接。因此,只要您从不接受子线程中的任何连接,而父线程继续接受这些连接,一切都将正常工作。
但是,如果您计划永远不接受子进程中的连接,为什么要在此进程中为套接字保留资源呢?
有趣的问题是,如果两个进程都在套接字上调用
accept()
,会发生什么。目前我找不到关于这个的确切信息。我能找到的是,你可以确定,每个连接只传递到其中一个进程。eqqqjvef3#
在
socket()
手册中,有一段话是这样说的:SOCK_CLOEXEC
在新的文件描述符上设置close-on-exec(
FD_CLOEXEC
)标志。请参阅open(2)
中O_CLOEXEC
标志的说明,了解这可能有用的原因。不幸的是,当你调用
fork()
时,它不起任何作用,只有当你调用execv()
和其他类似的函数时才起作用。无论如何,阅读open()
函数手册中的信息,我们会看到:O_CLOEXEC
(自Linux 2.6.23起)为新文件描述符启用close-on-exec标志。指定此标志允许程序避免额外的
fcntl(2)
F_SETFD
操作来设置FD_CLOEXEC
标志。请注意,在某些多线程程序中使用此标志是必不可少的,因为使用单独的X1 M12 N1 X1 M13 N1 X操作来设置X1 M14 N1 X标志不足以避免其中一个线程打开文件描述符并试图设置其close-on-exec标志使用
fcntl(2)
,同时另一个线程执行fork(2)
加execve(2)
。根据执行顺序,竞争可能导致由X1 M18 N1 X返回的文件描述符被无意地泄漏到由X1 M19 N1 X创建的子进程所执行的程序。(原则上,这种竞争对于任何创建文件描述符的系统调用都是可能的,该文件描述符的close-on-exec标志应该被设置,并且各种其他Linux系统调用提供了O_CLOEXEC
标志的等效物来处理这个问题。)好吧,那么这一切意味着什么?
这个想法很简单,如果你在调用
execve()
时让一个文件描述符保持打开状态,你就给予了子进程对那个文件描述符的访问权,这样它就可以访问它不应该访问的数据。当您创建
fork()
服务并执行代码时,该代码通常以删除权限开始(即,主Apache 2服务作为根运行,但是所有产生的X1 M23 N1 X实际上作为X1 M24 N1 X或X1 M25 N1 X用户运行-重要的是主进程是根以便打开端口80和443,1024以下的任何端口,现在,如果一个黑客能够控制这个子进程,他们至少不会访问这个文件描述符,如果它很早就关闭了。这要安全得多。另一方面,我的apache 2示例的工作方式不同:它首先打开套接字并将其绑定到端口80,443,然后用
fork()
创建子节点,每个子节点调用accept()
(默认情况下会阻塞)。第一个传入的连接将通过从accept()
调用返回来唤醒其中一个孩子。所以我猜那个毕竟没有那么危险。它甚至会保持该连接打开,并再次调用accept()
,直到您设置中定义的最大值(默认情况下大约是100,取决于您使用的操作系统)。在max.accept()
调用之后,该子进程退出,服务器创建一个新示例,这是为了确保内存占用不会增长太多。所以在你的情况下,它可能不是那么重要。然而,如果黑客接管了你的进程,他们可以接受其他连接,并用他们精明的服务器版本来处理它们......这是值得注意的。如果你的服务是内部的(只在你的内联网上运行),那么危险就小一些(尽管从我读到的资料来看,大多数公司里的小偷都是在那里工作的员工......)
ds97pgxw4#
子进程不会监听套接字,除非调用accept(),在这种情况下,传入的连接可以转到任一进程。
tcbh2hod5#
子进程从其父进程继承所有文件描述符。子进程应该关闭所有侦听套接字以避免与其父进程冲突。