nginx–基础–22–处理http请求头部的流程
1、接受请求事件模块
1.1、简化的流程
-
首先内核经过请求的三次握手成功后通过操作系统的负载均衡算法选择一个监听的worker进程
-
worker进程通过epoll事件机制epoll-wait方法返回一个链接句柄
-
事件模块epoll分配链接内存池 connection_pool_size:512字节
-
事件模块回调Http模块ngx_http_init_connection方法,并且添加超时定时器
-
事件模块epoll切换其它请求链接
-
当客户端发送真正的请求报文的时候,epoll事件模块接收到请求数据后http模块ngx_http_wait_request_handler从链接内存池分配内存存放请求报文 1k
1.2、细化的流程
- 建立连接:
- 当用户发来SYN的时候,内核会发送一个SYN|ACK表示确认了,然后当客户端再发来ACK的时候,内核认为这个连接已经建立成功了
- 选中worker
- 这假设nginx有很多worker进程,那么每个worker进程可能都监听了80端口,这个时候操作系统会根据它的负载均衡算法会选中CPU上的worker进程
- worker调用accept方法
- worker进程通过epoll_wait的方法去找到刚刚建立好的连接的句柄
- 拿到这句柄以后,这其实是一个读事件,因为我读到了ACK这样的一个报文,根据这个读事件,我们找到原来它是我们监听的80端口,然后我们就可以调用这个accept方法,
- 调用accept方法
- 分配连接内存池(connection_pool_size:512),之后我们所有的http模块开始从事件模块手中接入请求的处理过程
- ngx_http_init_connection
- http模块在启动的时候会定义一个ngx_http_init_connection设置回调方法
- 也就是说当accept一个新连接的时候,我们这个方法就被回调执行了,这个时候需要把新建立的这个事件的读事件添加到epoll中,通过epoll_ctl这个函数,然后还要加一个定时器
- client_header_timeout: 60s 如果60s钟没有收到请求就超时了
- 也就是说当accept一个新连接的时候,我们这个方法就被回调执行了,这个时候需要把新建立的这个事件的读事件添加到epoll中,通过epoll_ctl这个函数,然后还要加一个定时器
- http模块在启动的时候会定义一个ngx_http_init_connection设置回调方法
- 读取请求的数据
- 流程走完以后,可能nginx的事件模块就切换到其它的fd去处理了,当用户发送http请求的时候,其实它是发来data,那么在tcp层也就是操作系统内核层它会回一个ACK,但是同时事件模块的epoll_wait又拿到了一个请求,这个请求的回调方法是ngx_http_wait_request_handler,收到这个请求以后,我需要把内核中的DATA读到我nginx的用户态中,要读到用户态中需要从连接内存池分配内存,这个连接内存池中初始分配默认512字节;这个时候我需要从内存池中再分配1k,内存池是可以自动扩展大小的。
2、接受请求http模块
- 分配请求内存池
- 处理请求的时候尼,可能需要去做大量的上下文分析,去分析它的http协议,去分析每一个header;所以这个时候我需要分配一个请求内存池
- 默认 request_pool_size:4k ,基本上是connection_pool_size的8倍
- 因为请求的上下文它涉及到业务,通常4k是一个比较合适的一个数字,如果你分配的很少的时候,我们的请求内存池在不断的扩充,当我们分配内存的次数变的多的时候,性能肯定会下降的.
- 状态机解析请求行
1. 那么分配完这个内存池以后,我们会用一个状态机去解析请求行,在解析请求行的过程中可能会发现有的url特别大,已经超过了我们刚刚所分配的1k的大小,这个时候我们会分配一个大内存来解决url太长了的问题。 - 分配大内存
- large_client_header_buffers:4 8k
- 我们分配大内存的时候,并不是分配4*8=32K,它是先分配一个8k,然后把刚刚那个1k 的内容,拷贝到这个8k的第一部分,我们用剩下的7k再去接收httpd的url,如果8k都没有接收完,我们就会分配第二个8k,也就是分配了16k,那么最多我分配了32k
- 标识url
- 当我状态机解析请求行执行完后,就可以标识url
- 标识url
- 用一个指针去指向我们接收到的请求行
- 状态机解析header
- 标识完url后,接下接收header,http请求中的header可能会非常的长,因为它可能含有cookie,host那些字段。
- 状态机开始解析header,header是非常有可能超过1k的,这个时候又分配大内存(共用large_client_header_buffers)
- 标识header
- 分配完大内存以后,我接收到完整的header,就开始标识header了
- 标识header的过程中会涉及哪个server块开始处理这个请求
- 移除超时间定时器
- 标识完header以后,我就会移除我的超时间定时器
- client_header_timeout: 60s
- 标识完header以后,我就会移除我的超时间定时器