Nginx Nginx是如何处理 HTTP 头部的?_句柄

 

 

Nginx 处理 HTTP 头部的过程


在http模块处理请求之前,首先需要nginx的框架先对客户端建立好连接,然后接收用户发来的url,方法等,然后再去接收所有的header。根据这些header信息才能决定选择哪些配置块,才能决定让http模块如何处理请求。

当客户端发来SYN的时候,内核会回复SYN+ACK表示确认了,当客户端发来ACK,内核认为这个连接已经建立成功了。

Nginx有很多worker进程,多个进程都监听了80端口或者443端口。操作系统选择某个worker进程,这个worker进程根据epoll_wait方法返回刚刚建立好的连接的句柄。拿到建立好连接的句柄之后,这其实是一个读事件,因为我读到了ACK这样一个报文。之后调用accept方法分配连接内存池,在nginx当中主要分为连接内存池和请求内存池,这里就是开始分配连接内存池了,也就是说nginx会为这个新建立的连接分配512字节的内存池。

分配好内存池,建立好建立好连接之后,http模块开始从事件模块手中接入请求处理的过程了。http模块启动的时候就会定义了回调ngx_http_init_connection方法,也就是accept一个新连接的时候,这个方法就会被回调执行了。这个时候需要将新建立连接的读事件添加到epoll中,通过epollctl这个函数。同时添加一个定时器,在60s之内没有收到请求就超时了,也就是client_head_timeout,具体过程如下:

Nginx 在处理 HTTP 请求之前,首先需要 Nginx 的框架先和客户端建立好连接,然后接收用户发来的 HTTP 的请求行,比如方法、URL 等,然后接收所有的 Header,根据这些 Header 信息,才能决定由哪些 HTTP 模块处理请求。下面这张图,解释了 Nginx 在处理 HTTP 请求之前,所经历的一系列流程,强烈建议收藏保存。下面针对每个部分单独讲解一下。


Nginx Nginx是如何处理 HTTP 头部的?_客户端_02

​                                                                     Nginx接收HTTP请求流程图.png

 

接收请求事件模块


Nginx Nginx是如何处理 HTTP 头部的?_nginx_03

首先是三次握手,当客户端发来 ACK 之后,由操作系统内核回一个 SYN+ACK,紧接着客户端 ACK 之后,连接建立成功。同时可能有很多 worker 进程都在监听 80 或 443 端口,由操作系统的负载均衡算法,选取一个 worker 进程来处理,这个 worker 进程会通过 ​​epoll_wait​​​ 方法,返回一个建立连接的句柄。拿到了监听的句柄之后,这实际上是一个读事件(因为是从操作系统中读取到了一个请求),调用 ​​accept​​ 方法,分配连接内存池。

内存池主要分为连接内存池和请求内存池。

连接内存池大小的配置是 ​​connection_pool_size​​​,到了这一步之后,Nginx 会为已经建立的连接分配一个 512 字节大小的连接内存池。分配完内存池,建立好连接之后,HTTP 模块会从事件模块手里接入请求处理的过程,HTTP 模块在启动时,会调用 ​​ngx_http_init_connection​​​ 方法来设置回调方法,这个时候会把新建立连接的读事件通过 ​​epoll_ctl​​​ 函数添加到 epoll 中,然后加一个超时定时器 ​​client_header_timeout: 60s​​,这个定时器的作用是,如果超过 60s 还没有接收到客户端发来的请求,那么就会断开连接。这一部分走完之后,Nginx 的事件模块可能就会切换到其他的句柄去处理了。

Nginx Nginx是如何处理 HTTP 头部的?_句柄_04

当用户真的把请求发来之后,操作系统会回复一个 ACK,同时事件模块的 ​​epoll_wait​​​ 也拿到了这个请求,这个时候会调用设置的回调方法 ​​ngx_http_wait_request_handler​​,将接收到的用户请求读到用户态中,而读取到用户态中需要操作系统分配内存,那么这段内存分配多大?从哪里分配呢?

这段内存是从连接内存池分配的,初始虽然分配了 512 字节,但是内存池可以扩展,由 ​​client_header_buffer_size: 1k​​ 分配 1k 内存,内存池并不是越大越好,因为用户即使发送了 1 个字节,也会分配出 1k 的内存出来。当 URL 超过 1k 后,应该怎么办呢?

 

接收请求 HTTP 模块


                  ​

Nginx Nginx是如何处理 HTTP 头部的?_句柄_05

处理请求和处理连接是不一样的,处理请求只需要放到 Nginx 内存中就行了,但是处理请求还需要做大量的上下文分析,所以要分配一个请求内存池 ​​request_pool_size: 4k​​​。分配完以后,状态机开始解析请求行,如果这时候发现 URL 大于 4k,那么就会再分配一个大内存,也就是 ​​large_client_header_buffers: 4 8k​​,这个配置的意思是说,最多分配 4 个 8k,它并不是一次性分配 32k,而是先分配 8k 然后再去解析请求行,如果依然大于 8k,那么就会再分配 8k 的内存。

Nginx 有很多变量,这些变量都是指针,其中可以用来标识 URI,标识完成之后,就开始处理 header。状态机解析 header 的时候,如果发现内存不够,也就是假如 URL 已经用掉了 ​​large_client_header_buffers: 4 8k​​ 中的 2 个 8k,这时候最多也只能分配 8k,请求行和 header 是公用 4 个 8k的。

分配完大内存之后,就开始标识 header,确定哪一个 server 块去处理请求,然后移除超时定时器,接下来,就开始核心的 11 个阶段 HTTP 请求处理请求。

这里需要注意以下几个地方:

  • 连接内存池:初始大小 512 字节
  • ​client_header_buffer_size: 1k​​ 从连接内存池中分配
  • ​large_client_header_buffers: 4 8k​​ 也是从连接内存池中分配
  • 请求内存池:​​request_pool_size: 4k​