Nginx是一款集静态资源、负载均衡于一身的高性能Web服务器其特点是, 占有内存少,并发能力强。

关于Nginx的基本使用参考之前写过的文章:nginx的基本使用说明

学习目标

  1. 配置环境,源码安装nginx
  2. 了解configure文件的作用,如何定制模块
  3. 学习nginx的配置文件结构
  4. 认识nginx的启动,使用命令
  5. 初步认识nginxmain()函数的基本功能

相关环境配置

Nginx版本下载地址: nginx-1.14.2

建立了GitHub源码学习地址,主要是一些注释记录:nginx-1.14.2-annotated

编译环境是一台云服务器:Ubuntu Server 18.04.1 LTS 64位 1核 2GB 1Mbps,编译工具为: gcc version 7.4.0

使用了两种编辑器:

  1. 通过xshell/iTerm2远程 + vim 8.0.1453(部分系统自带7版本,建议更新到vim 8版本才能使用某些插件), vim插件基本配置参考文章: vim插件配置
  2. 另一些时候使用vscode+remote插件。

源码目录结构

目录 说明
src目录 存放nginx源码。
man目录 存放nginx帮助文档。
html目录 存放默认网站文件。
contrib目录 存放其它机构或组织贡献的文档资料。
conf目录 存放nginx服务器的配置文件。
auto目录 存放大量的脚本文件,和configure脚本程序相关。
configure文件 Nginx自动安装脚本,用于环境检查,生成编译代码需要的makefile文件。
CHANGES,CHANGES.ru,LICENSE和README 都是Nginx服务器的相关资料。

编译安装

依赖库安装(默认安装了gcc):

1
2
3
4
sudo apt-get update
sudo apt-get install build-essential
sudo apt-get install libtool
sudo apt-get install libpcre3 libpcre3-dev zlib1g-dev openssl

极简安装,下一节会说明configure文件的作用。

1
2
3
4
tar -zxvf nginx-1.14.2.tar.gz
cd nginx-1.14.2
./configure --prefix=/usr/local/nginx
make && make install

configure脚本

在编译安装环境可以看到./configure --prefix=/usr/local/nignx指定nginx编译安装位置,除了该参数外configure还支持非常多的参数,可以通过./configure --help查看。由于支持的参数太多,这里不一一列举了。这里并不需要记住所有参数,只需要知道在执行./configure时,可以通过添加配置这些参数来决定configure的行为。

nginx的configure由shell脚本编写,执行过程中会调用/auto/目录下的脚本。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/bin/sh

# Copyright (C) Igor Sysoev
# Copyright (C) Nginx, Inc.

LC_ALL=C
export LC_ALL

#options脚本定义后续工作需要用到的变量
#根据本次调用configure时携带的参数与默认值设置这些变量
. auto/options
#init脚本初始化后续工作需要的一些文件路径
. auto/init
#sources分析nginx源码结构,该脚本将.c/.h代码文件分类,并定义为对应的变量数组
. auto/sources

# 测试并创建$NGX_OBJS目录,该目录用来存储编译过程中所有目标文件的路径
test -d $NGX_OBJS || mkdir -p $NGX_OBJS

# 以下两个变量定义在auto/init脚本里,分别是ngx_auto_headers.h,autoconf.err文件
echo > $NGX_AUTO_HEADERS_H
echo > $NGX_AUTOCONF_ERR
# 向ngx_auto_headers.h文件写入命令行参数
echo "#define NGX_CONFIGURE \"$NGX_CONFIGURE\"" > $NGX_AUTO_CONFIG_H

# 关于DEBUG宏
if [ $NGX_DEBUG = YES ]; then
    have=NGX_DEBUG . auto/have
fi

# checking for os
if test -z "$NGX_PLATFORM"; then
    echo "checking for OS"

    NGX_SYSTEM=`uname -s 2>/dev/null`
    NGX_RELEASE=`uname -r 2>/dev/null`
    NGX_MACHINE=`uname -m 2>/dev/null`
    # 输出操作系统相关信息
    echo " + $NGX_SYSTEM $NGX_RELEASE $NGX_MACHINE"

    NGX_PLATFORM="$NGX_SYSTEM:$NGX_RELEASE:$NGX_MACHINE";

    case "$NGX_SYSTEM" in
        MINGW32_* | MINGW64_* | MSYS_*)
            NGX_PLATFORM=win32
        ;;
    esac

else
    echo "building for $NGX_PLATFORM"
    NGX_SYSTEM=$NGX_PLATFORM
fi

# 检查并设置编译器
. auto/cc/conf

# 非win操作系统定义一些头文件
if [ "$NGX_PLATFORM" != win32 ]; then
    . auto/headers
fi
# 对于当前的操作系统,设置操作系统特定的一些方法并检查是否支持。
# 例如linux特有的sendfile系统调用来加速向网络中发送文件块。
. auto/os/conf

# unix/linux操作系统相关的头文件与系统调用设置
if [ "$NGX_PLATFORM" != win32 ]; then
    . auto/unix
fi

# 线程相关的支持设置
. auto/threads
##########################
# 最核心的构造部分
# 生成ngx_modules.c文件
# 定义了ngx_modules数组
##########################
. auto/modules
# 在链接时需要的第三方静态,动态库或者目标文件是否存在
. auto/lib/conf

# nginx安装后的路径
case ".$NGX_PREFIX" in
    .)
        NGX_PREFIX=${NGX_PREFIX:-/usr/local/nginx}
        have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define
    ;;

    .!)
        NGX_PREFIX=
    ;;

    *)
        have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define
    ;;
esac

# nginx安装后conf文件的路径
if [ ".$NGX_CONF_PREFIX" != "." ]; then
    have=NGX_CONF_PREFIX value="\"$NGX_CONF_PREFIX/\"" . auto/define
fi

# nginx安装后,其它一些文件路径参数设置
have=NGX_SBIN_PATH value="\"$NGX_SBIN_PATH\"" . auto/define
have=NGX_CONF_PATH value="\"$NGX_CONF_PATH\"" . auto/define
have=NGX_PID_PATH value="\"$NGX_PID_PATH\"" . auto/define
have=NGX_LOCK_PATH value="\"$NGX_LOCK_PATH\"" . auto/define
have=NGX_ERROR_LOG_PATH value="\"$NGX_ERROR_LOG_PATH\"" . auto/define

have=NGX_HTTP_LOG_PATH value="\"$NGX_HTTP_LOG_PATH\"" . auto/define
have=NGX_HTTP_CLIENT_TEMP_PATH value="\"$NGX_HTTP_CLIENT_TEMP_PATH\""
. auto/define
have=NGX_HTTP_PROXY_TEMP_PATH value="\"$NGX_HTTP_PROXY_TEMP_PATH\""
. auto/define
have=NGX_HTTP_FASTCGI_TEMP_PATH value="\"$NGX_HTTP_FASTCGI_TEMP_PATH\""
. auto/define
have=NGX_HTTP_UWSGI_TEMP_PATH value="\"$NGX_HTTP_UWSGI_TEMP_PATH\""
. auto/define
have=NGX_HTTP_SCGI_TEMP_PATH value="\"$NGX_HTTP_SCGI_TEMP_PATH\""
. auto/define

# 创建编译时使用的objs/Makefile文件
. auto/make
# 为objs/Makefile加入需要连接的第三方静态库,动态库或者目标文件
. auto/lib/make
# 为objs/Makefile加入install功能,当执行make
# install时将编译生成的必要文件复制到安装路径,建立必要的目录
. auto/install

# STUB
# 在ngx_auto_config.h文件中加入NGX_SUPPRESS_WARN宏,NGX_SMP宏
. auto/stubs

# 在ngx_auto_config.h文件中指定NGX_USER和NGX_GROUP宏,
# 如果执行configure时没有参数指定,
# 默认两者皆为nobody(也就是默认以nobody用户运行进程)
have=NGX_USER value="\"$NGX_USER\"" . auto/define
have=NGX_GROUP value="\"$NGX_GROUP\"" . auto/define

if [ ".$NGX_BUILD" != "." ]; then
    have=NGX_BUILD value="\"$NGX_BUILD\"" . auto/define
fi

# 显示configure执行的结果,如果失败,则给出失败原因
. auto/summary

configure生成的文件

configure执行成功时会生成objs目录,并在该目录下生成以下目录和文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
├── autoconf.err        //保存configure执行过程中产生的结果
├── Makefile            //用于编译nginx工程及install参数安装nginx程序
├── ngx_auto_config.h   //会被src/core/ngx_config.h和
├── ngx_auto_headers.h  //src/os/unix/ngx_linux_config.h文件引用
├── ngx_modules.c       //这是一个关键文件,下边展开讲
└── src                 //src目录用于存放编译时产生的目标文件
    ├── core
    ├── event
    │   └── modules
    ├── http
    │   ├── modules
    │   │   └── perl
    │   └── v2
    ├── mail
    ├── misc
    ├── os
    │   ├── unix
    │   └── win32
    └── stream

ngx_modules.c文件

configure除了生成Makefile文件外,还生成了ngx_modules.c文件,该文件定义了ngx_modules数组,该数组指明了每个模块在nginx中的优先级,当一个请求同时符合多个模块的处理规则时,将按照ngx_modules数组中的顺序选择最靠前的模块优先处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <ngx_config.h>
#include <ngx_core.h>

extern ngx_module_t  ngx_core_module;
extern ngx_module_t  ngx_errlog_module;
//更多参数....

ngx_module_t *ngx_modules[] = {
    &ngx_core_module,
    &ngx_errlog_module,
    &ngx_conf_module,
    &ngx_regex_module,
    &ngx_events_module,
    &ngx_event_core_module,
    &ngx_epoll_module,
    &ngx_http_module,
    &ngx_http_core_module,
    &ngx_http_log_module,
    NULL    //省略....
};
//其它.....

nginx命令

nginx命令可以通过nginx -hnginx -?查看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
nginx -?
nginx version: nginx/1.14.2
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]

Options:
  -?,-h         : this help
  -v            : show version and exit
  -V            : show version and configure options then exit
  -t            : test configuration and exit
  -T            : test configuration, dump it and exit
  -q            : suppress non-error messages during configuration testing
  -s signal     : send signal to a master process: stop, quit, reopen, reload
  -p prefix     : set prefix path (default: /usr/local/nginx/)
  -c filename   : set configuration file (default: conf/nginx.conf)
  -g directives : set global directives out of configuration file

nginx启动

  • 默认启动方式:/usr/local/nginx/sbin/nginx
  • 指定配置文件启动:/usr/local/nginx/sbin/nginx -c /tmp/nginx.conf
  • 另行指定安装目录的启动方式: nginx -p /usr/local/nginx
  • 另行指定全局配置项的启动方式: nginx -g "pid /var/nginx/test.pid;"

测试配置信息与显示查看信息

1
2
3
4
nginx -t        //不启动nginx的情况下,测试配置文件是否有错误
nginx -t -q     //测试配置选项时,-q参数屏蔽error级别以下的错误到屏幕上
nginx -v        //显示nginx版本信息
nginx -V        //显示编译阶段的参数

停止nginx

快速停止服务:

1
2
3
4
nginx -s stop   //快速关闭nginx
//也可以执行kill命令来停止
kill -s SIGTERM pid
kill -s SIGINT pid

优雅的(nginx服务正常地处理完当前所有请求在)停止服务:

1
2
3
4
nginx -s quit   //处理完当前所有请求在停止服务
kill -s SIGOUIT pid
//如果希望优雅的停止某个worker进程
kill -s SIGWINCH pid

其它

重新读取配置文件并生效

1
2
nginx -s reload
kill -s SIGHUP pid

日志回滚:

1
2
nginx -s reopen
kill -s SIGUSR1 pid

平滑升级(不重启nginx服务的情况下来完成新版本的升级):

  1. 通知正在运行的旧版本nginx准备升级: kill -s SIGUSR2 pid。运行中的nginx会将pid文件重命名。
  2. 启动新版本的nginx。
  3. 通过kill命令向旧版本的master进程发生SIGQUIT信号来优雅的关闭旧版本的nginx,随后只有新版本的nginx服务运行。

nginx配置文件

nginx.conf通用配置结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
...         # 全局块
events {    # events块
    ...
}

http {      # http块
    ...     # http全局块
    server {    # server块
        ...     # server全局块
        location [PATTERN] {    # location块
            ...
        }
        location [PATTERN] {
            ...
        }
    }
    server {
        ...
    }
    ...     # http全局块
}
...
  1. 全局块:配置影响nginx全局的指令。一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等。
  2. events块:配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。
  3. http块:可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。
  4. server块:配置虚拟主机的相关参数,一个http中可以有多个server。
  5. location块:配置请求的路由,以及各种页面的处理情况。

关于Nginx的配置文件说明参考之前写过的文章:nginx的基本使用说明。这里不重复叙述了。

main()函数

main()函数在src/core/nginx.c文件中,是nginx程序启动后的入口函数。

ngx_get_options函数解析命令行参数,根据参数的含义,赋值对应的全局变量,后续函数通过全局变量的取值决定是否执行相应的操作。 当命令行参数是-g -c -p -s时,后续的参数将存储在对应的变量中,供后续程序使用。

1
2
3
if (ngx_get_options(argc, argv) != NGX_OK) {
    return 1;
}

日志初始化,这里的日志是临时日志,而非配置文件中的日志。该日志主要用来处理nginx主进程启动过程中的一些日志记录。

1
2
3
4
log = ngx_log_init(ngx_prefix);
if (log == NULL) {
    return 1;
}

ngx_cycle_t结构体是nginx中一个非常核心的结构体,许多功能都是该结构体的成员。这里初始化一个该类型的局部变量init_cycle并使一个全局指针ngx_cycle指向它。在将上一节初始化的临时日志赋予该结构的log成员。

1
2
3
4
5
6
7
/*
* init_cycle->log is required for signal handlers and
* ngx_process_options()
*/
ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
init_cycle.log = log;
ngx_cycle = &init_cycle;

将命令行参数保存到全局变量ngx_argv数组中。如果保存过程中有异常发送,记录到init_cycle.log日志中。

1
2
3
if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
    return 1;
}

将prefix等信息保存到init_cycle.prefix等对应成员中。

1
2
3
if (ngx_process_options(&init_cycle) != NGX_OK) {
    return 1;
}

初始化操作系统相关信息,并保存到对应的全局变量中。

1
2
3
if (ngx_os_init(log) != NGX_OK) {
    return 1;
}

又是一个初始化,当前还不知道是什么作用。

1
2
3
4
5
6
/*
* ngx_crc32_table_init() requires ngx_cacheline_size set in ngx_os_init()
*/
if (ngx_crc32_table_init() != NGX_OK) {
    return 1;
}

slab内存算法相关初始化,具体原理当前不清楚。

1
2
3
4
/*
* ngx_slab_sizes_init() requires ngx_pagesize set in ngx_os_init()
*/
ngx_slab_sizes_init();

这里处理平滑升级相关(不重启升级),该新master进程通过环境变量继承相关信息。

1
2
3
if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
    return 1;
}

处理configure生成的ngx_modules模块,按顺序排序赋值index和name字段。

1
2
3
if (ngx_preinit_modules() != NGX_OK) {
    return 1;
}

核心初始化部分,放到后面展开。

1
2
3
4
5
6
7
8
9
/* nginx核心初始化 */
cycle = ngx_init_cycle(&init_cycle);
if (cycle == NULL) {
    if (ngx_test_config) {
         ngx_log_stderr(0, "configuration file %s test failed",
                init_cycle.conf_file.data);
    }
    return 1;
}

打开配置文件,找到对应.pid文件,读取pid,调用ngx_os_signal_process()发送信号

1
2
3
if (ngx_signal) {   /* 处理-s参数相关的业务 */
    return ngx_signal_process(cycle, ngx_signal);
}

初始化信号处理模块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
if (ngx_init_signals(cycle->log) != NGX_OK) {
    return 1;
}

if (!ngx_inherited && ccf->daemon) {
    if (ngx_daemon(cycle->log) != NGX_OK) {
        return 1;
    }

    ngx_daemonized = 1;
}

if (ngx_inherited) {
    ngx_daemonized = 1;
}

关闭临时log日志。

1
2
3
4
5
6
if (log->file->fd != ngx_stderr) {
    if (ngx_close_file(log->file->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                    ngx_close_file_n " built-in log failed");
    }
}

进入nginx.conf中配置的工作模式。

1
2
3
4
5
6
7
if (ngx_process == NGX_PROCESS_SINGLE) {
    ngx_single_process_cycle(cycle);
    } else {    /* 多进程模式 */
    ngx_master_process_cycle(cycle);
    }
    return 0;
}

ngx_init_cycle

1
2
3