基本概念:
Nginx作为一款开源的、高性能的HTTP服务器和反向代理服务器而闻名,本文基于nginx-1.15.0,将为读者简要介绍其HTTP处理流程。
通常nginx配置文件如下所示:
worker_processes 1;
events {
worker_connections 1024;
}
http{
access_log logs/access.log main;
server {
listen 80;
server_name example.com;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
}
}
}
- Nginx采用master-worker编模式,master初始化配置,创建Socket并监听端口,启动并管理worker进程,worker进程负责接收客户端请求并提供服务;其中程worker_processes配置的就是worker进程的数目;
- events指令块用于配置事件处理相关,比如worker_connections用于配置每个worker进程最大维护的socket链接数目;
- http指令块用于配置http请求处理相关,比如access_log用于配置access日志文件路径;
- server指令块用于配置virtual server,通常会在一台机器配置多个virtual server,监听不同端口号,映射到不同文件目录;比如listen可配置监听端口号;
- location指令块配置不同路径请求处理方式,比如proxy_pass可配置将请求按照http协议格式转发给上游,fastcgi_pass可配置将请求按照fastcgi协议转发给fpm处理。
Nginx高度模块化,每个模块实现某一具体功能,比如ngx_http_limit_req_module模块实现按请求速率限流功能,ngx_http_fastcgi_module模块实现fastcgi协议通信功能。每个模块都需要解析配置文件中相关配置,每个模块需要解析的所有配置都定义为ngx_command_t数组。
例如ngx_http_module模块,其ngx_command_t数定义如下:
struct ngx_command_s {
ngx_str_t name;
ngx_uint_t type;
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
...
};
static ngx_command_t ngx_http_commands[] = {
{ ngx_string("http"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_http_block,
...
},
};
- name指令名称,解析配置文件时按照名称能匹配查找;
- type指令类型,NGX_CONF_NOARGS标识该配置无参数,NGX_CONF_BLOCK该配置是一个配置块,NGX_MAIN_CONF表示配置可以出现在哪些位置(NGX_MAIN_CONF、NGX_HTTP_SRV_CONF、NGX_HTTP_LOC_CONF);
- set指令处理函数;
初始化服务器
http指令块用于配置http请求处理相关,解析http指令的处理函数为ngx_http_block,实现如下:
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
//解析main配置
//解析server配置
//解析location配置
//初始化HTTP处理流程所需的handler
//初始化listening
if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
函数ngx_http_block主要解析http块内部的main配置、server配置与location配置;同时会初始化HTTP处理流程所需的handler;以及初始化所有监听端口。
函数ngx_http_optimize_servers将所有配置的IP端口进一步解析,并存储在conf->cycle->listening字段,这是一个数组,后续操作会遍历此数组,创建socket并监听。
conf->cycle->listening数组元素类型为ngx_listening_t,创建该ngx_listening_t对象时,同时会设置其处理handler为函数ngx_http_init_connection,当接受到客户端链接请求时,会调用此handler。
那么什么时候启动监听呢?全局搜索关键字cycle->listening可以找到。main方法会调用ngx_init_cycle,其完成了服务器初始化的大部分工作,其中就包括启动监听(ngx_open_listening_sockets):
ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle)
{
for (i = 0; i < cycle->listening.nelts; i++) {
s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);
bind(s, ls[i].sockaddr, ls[i].socklen);
listen(s, ls[i].backlog);
}
}
假设nginx使用epoll处理所有socket事件,那么什么时候将监听事件添加到epoll呢?同样全局搜索关键字cycle->listening可以找到。ngx_event_core_module模块是事件处理核心模块,初始化此模块时会执行ngx_event_process_init函数,从而将监听事件添加到epoll:
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
//设置读事件处理handler
rev->handler = ngx_event_accept;
ngx_add_event(rev, NGX_READ_EVENT, 0);
}
}
注意到向epoll添加读事件时,设置该读事件处理函数为ngx_event_accept,即接收到客户端socket连接请求事件时会调用该处理函数。
HTTP请求解析
基础结构体
结构体ngx_connection_t存储socket连接相关信息;nginx预先创建若干个ngx_connection_t对象,