Nginx 的TCP 实现简析

作为Web服务器的nginx,主要任务当然是处理好基于TCP的HTTP协议,我们就协议的实现细节(linux下)以更好地理解Nginx事件处理机制。先来回顾下TCP的三次握手过程:

1)客户端向服务器发起连接(SYN)。

2)服务器确认收到并向客户端也发起连接(ACK+SYN)。

3)客户端确认收到服务器发起的连接(ACK)。

这个建立连接的过程是在操作系统内核中完成的,而如Nginx这样的应用程序只是从内核中取出已经建立好的TCP连接。大多时候,Nginx是作为连接的服务器方存在的,我们看一看Linux内核是怎样处理TCP连接建立的,首先Nginx 在fork 出work子进程来一起监听端口,内核在我们调用listen方法时,就已经为这个监听端口建立了SYN队列和ACCEPT队列,当客户端使用connect方法向服务器发起TCP连接,随后客户端的SYN包到达了服务器后,内核会把这一信息放到SYN队列(即未完成握手队列)中,同时回一个SYN+ACK包给客户端。客户端再次发来了针对服务器SYN包的ACK网络分组时,内核会把连接从SYN队列中取出,再把这个连接放到ACCEPT队列(即已完成握手队列)中。而服务器在第3步调用accept方法建立连接时,其实就是直接从ACCEPT队列中取出已经建好的连接而已。

这样,如果大量连接同时到来,而应用程序不能及时地调用accept方法,就会导致以上两个队列满(ACCEPT队列满,进而也会导致SYN队列满),从而导致连接无法建立。这其实很常见,比如Nginx的每个worker进程都负责调用accept方法,如果一个Nginx模块在处理请求时长时间陷入了某个方法的执行中(如执行计算或者等待IO),就有可能导致新连接无法建立。

本身tcp会建立一套稳定可靠的字节接收服务,另外内核为每一个TCP连接分配了内存用来发送,接收缓冲区,Nginx作为一个事件驱动的服务,当epoll 模块通知work进程接收到了网络报文,Nginx使用好TCP协议主要在于如何有效率地使用CPU和内存。只在必要时才调用TCP的send/recv方法,这样就避免了无谓的CPU浪费,同样,只在发送缓冲区有空闲空间时才去调用send方法,这样的调用才是有效率的。

还有关于内存的分配,内核在读写数据包都会分配缓存,写缓存的实际内存大小与场景有关。对读缓存来说,接收到一个来自连接对端的TCP报文时,会导致读缓存增加,如果超过了读缓存上限,那么这个报文会被丢弃。当进程调用read、recv这样的方法读取字节流时,读缓存就会减少。因此,读缓存是一个动态变化的、实际用到多少才分配多少的缓冲内存。当用户进程调用send方法发送TCP字节流时,就会造成写缓存增大。当然,如果写缓存已经到达上限,那么写缓存维持不变,向用户进程返回失败。而每当接收到连接对端发来的ACK,确认了报文的成功发送时,写缓存就会减少。可见缓存上限所起作用为:丢弃新报文,防止这个TCP连接消耗太多的内存。

下一篇研究下内核对网卡数据流的控制

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181108G11TV900?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券