Web服务器如何“侦听” IP地址,中断或轮询?


87

我正在尝试了解Web服务器的较低细节。我想知道服务器(例如Apache)是否正在持续轮询新请求,或者服务器是否可以通过某种中断系统工作。如果是中断,是什么引发了该中断,这是网卡驱动程序吗?


1
要理解的关键字是“ server”。在服务器-客户端模型(相对于主从模型)中,服务器等待来自客户端的请求。这些请求是需要提供服务的事件。Web服务器是一个应用程序。您的问题是将应用程序软件与硬件术语(例如,中断和NIC)结合在一起,而不是将相关概念保留在同一抽象层。NIC驱动程序有时有时实际上会使用轮询,例如,当有大量数据包时,Linux NAPI驱动程序会退回到轮询。但这与事件处理应用程序SW无关。
锯末2014年

1
@sawdust非常有趣。这个问题实际上是为了理解软件和硬件过程之间的联系
user2202911 2014年

1
这与命令行(和其他GUI)程序监听键盘的方式非常相似。尤其是在窗口系统中,您需要执行以下步骤:内核从键盘设备接收数据并将其交给窗口管理器,该窗口管理器识别具有焦点的窗口并将数据提供给该窗口。
G-Man

@ G-Man:我说是的。实际上,大多数打字员不会以1 Gbit / s的速度打字,这证明拥有两种不同的体系结构是合理的。一种干净,灵活且缓慢,一种笨拙但高速。
MSalters 2014年

Answers:


181

简短的答案是:某种中断系统。本质上,他们使用阻塞I / O,这意味着他们在等待新数据时会休眠(阻塞)。

  1. 服务器创建一个监听套接字,然后在等待新连接时阻塞。在这段时间内,内核将进程置于可中断的睡眠状态并运行其他进程。这一点很重要:连续进行进程轮询会浪费CPU。内核可以通过阻止进程直到有工作要做,才能更有效地使用系统资源。

  2. 当新数据到达网络时,网卡将发出一个中断。

  3. 看到网卡有中断,内核通过网卡驱动程序从网卡读取新数据并将其存储到内存中。(这必须快速完成,并且通常在中断处理程序中进行处理。)

  4. 内核处理新到达的数据并将其与套接字关联。在该套接字上阻塞的进程将被标记为可运行,这意味着它现在可以运行了。它不一定立即运行(内核可能决定仍然运行其他进程)。

  5. 内核会在闲时唤醒被阻止的Web服务器进程。(因为它现在可以运行了。)

  6. Web服务器进程将继续执行,就像没有时间过去一样。它的阻塞系统调用返回并处理任何新数据。然后...转到步骤1。


18
+1可以清楚地描述内核与网络服务器进程。
罗素·博罗戈夫

13
我不敢相信可以如此清晰,简单地概括出如此复杂的事情,但是您做到了。+1
布兰登2014年

8
+1好答案。此外,使用现代的NIC,OS和驱动程序,第2步和第3步之间的步骤可能会更加复杂。例如,使用Linux 上的NAPI,实际上不会在中断上下文中接收到数据包。相反,内核会说:“好的NIC,我知道您有数据。请退出bug我(禁用中断源),然后我很快就会回来抓取此数据包以及在此之前可能到达的所有后续数据包。”
Jonathon Reinhart 2014年

8
轻微的问题:确实没有必要进行阻止。一旦服务器进程创建了侦听套接字,即使您未在其中阻塞,内核也将在该端口上接受SYN accept。它们是(幸运的是,或将完全糟透了!)独立的异步运行任务。随着连接的进入,它们被放入一个队列中,accept从中拉出它们。只有不存在时,它才会阻塞。
戴蒙2014年

3
“从网卡读取新数据并将其存储到内存中。(这必须快速完成,并且通常在中断处理程序中进行处理。)”是否通过直接内存访问来完成?
思源仁

9

有很多“较低”的细节。

首先,请考虑内核具有进程列表,并且在任何给定时间,这些进程中有一些正在运行,而有些则没有。内核允许每个正在运行的进程占用一部分CPU时间,然后中断它并移至下一个。如果没有可运行的进程,则内核可能会向CPU 发出类似HLT的指令,从而使CPU挂起,直到发生硬件中断为止。

服务器中某处的系统调用显示“给我一些事情”。有两种大致的实现方法。对于Apache,它调用acceptApache先前打开的套接字,可能监听端口80。内核维护一个连接尝试队列,并在每次接收到TCP SYN时将其添加到该队列中。内核如何知道已接收到TCP SYN取决于设备驱动程序。对于许多NIC,接收网络数据时可能会发生硬件中断。

accept请内核将我的下一个连接初始化返回给我。如果队列不为空,则accept立即返回。如果队列为空,那么将从正在运行的进程列表中删除该进程(Apache)。稍后启动连接时,该过程将恢复。这称为“阻止”,因为对于调用它的进程而言,它accept()看起来像一个函数,直到它产生结果后才返回,这可能要从现在开始。在这段时间内,该过程无能为力。

一旦accept返回,Apache就会知道有人正在尝试建立连接。然后,它将调用fork将Apache进程拆分为两个相同的进程。这些过程之一继续处理HTTP请求,其他过程accept再次调用以获取下一个连接。因此,总会有一个主进程,除了调用accept和生成子进程外什么都不做,然后每个请求都有一个子进程。

这是一个简化:可以使用线程而不是进程来执行此操作,并且还可以fork预先执行,这样在收到请求时便有一个工作进程可供使用,从而减少了启动开销。取决于Apache的配置方式,它可以执行以下任一操作。

这是执行此操作的第一大类,它称为阻塞IO,因为系统调用like acceptreadand write在套接字上进行操作将挂起进程,直到有返回值为止。

另一种广泛的实现方式称为非阻塞或基于事件或异步IO。这是通过系统调用(例如select或)实现的epoll。它们每个都做同样的事情:给它们一个套接字列表(或一般来说,文件描述符),以及它们要使用的功能,内核阻塞直到它准备做其中一件事情。

使用此模型,您可能会告诉内核(带有epoll),“告诉我端口80上是否有新连接,或者要从我打开的这9471个其他连接中的任何一个上读取新数据。” epoll直到这些事情之一准备就绪,然后再做。然后重复。系统调用像acceptreadwrite永不块,部分是因为当你打电话给他们,epoll只是告诉你,他们已经准备好所以会是没有任何理由阻止,而且还因为当你打开插座或您指定的文件,你希望他们在非阻塞模式下,因此这些调用将失败,EWOULDBLOCK而不是阻塞。

该模型的优点是您只需要一个过程。这意味着您不必为每个请求分配堆栈和内核结构。NginxHAProxy使用此模型,这是一个主要原因,在相似的硬件上,它们可以处理比Apache多得多的连接。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.