Nginx学习笔记

1. 编译步骤

1.1 configure原理

configure本质上是个shell脚本,所以如果要完全理解configure需要熟悉shell基本语法,除此之外,脚本中大量运用了test、sed、cat、echo、grep等命令以及重定向符,所以也需要了解这些命令的用法。

1.2 auto脚本

auto脚本由一系列脚本组成,他们有一些是实现一些通用功能由其它脚本来调用(如have),有一些则是完成一些特定的功能(如option)。脚本之间的主要执行顺序及调用关系如下图所示(由上到下,表示主流程的执行):

上图中的脚本都位于auto目录下,所以省略,而configure与auto同级目录,故完整执行脚本如.auto/options所示。

1.2.1 auto/options脚本

auto/options主是处理用户输入的configure选项,以及输出帮助信息等。auto/options的目的主要是处理用户选项,并由选项生成一些全局变量的值,这些值在其它文件中会用到。该文件也会输出configure的帮助信息。
应当注意如下代码

1
2
3
for option
do
opt="$opt `echo $option | sed -e \"s/\(--[^=]*=\)\(.* .*\)/\1'\2'/\"`"

for后面可以省略要遍历的变量,这时,表示在遍历$@,即用户传入的所有变量组合

1.2.2 auto/init脚本

该文件的目录在于初始化一些临时文件的路径,检查echo的兼容性,并创建最原始的Makefile文件。

注意:这里生成的Makefile文件与configure文件在同一目录,真正执行编译指令的Makefile在objs目录下

1.2.3 auto/sources脚本

该文件从文件名中就可以看出,它的主要功能是跟源文件相关的。它的主要作用是定义不同功能或系统所需要的文件的变量。根据功能,分为CORE/REGEX/EVENT/UNIX/FREEBSD/HTTP等。每一个功能将会由四个变量组成,”_MODULES”表示此功能相关的模块,最终会输出到ngx_modules.c文件中,即动态生成需要编译到nginx中的模块;”INCS”表示此功能依赖的源码目录,查找头文件的时候会用到,在编译选项中,会出现在”-I”中;”DEPS”显示指明在Makefile中需要依赖的文件名,即编译时,需要检查这些文件的更新时间;”SRCS”表示需要此功能编译需要的源文件。

根据上面的描述,所以如果需要对nginx进行功能扩展,添加相应的C文件后,修改这个脚本就可以将自己的功能编译进nginx。

2. 源码解析

2.1 模块相关

ngx_module.h中引用了两个至关重要的外部变量

1
2
extern ngx_module_t  *ngx_modules[];
extern char *ngx_module_names[];

ngx_modulesngx_module_names定义在ngx_modules.c中,这个文件并不存在于nginx源码中,在编译时执行configure动态生成。ngx_modules数组包含所有的Nginx模块,Nginx启动时会调用ngx_cycle_modules函数,原型如下:

1
ngx_int_t ngx_cycle_modules(ngx_cycle_t *cycle)

此函数的目的是将ngx_modules中的数据复制到cycle中,函数调用顺序如下:

main->ngx_init_cycle->ngx_cycle_modules

2.2 数据结构

2.2.1 ngx_str_t

Nginx开发从入门到精通已经对ngx_str_t的定义以及相关api进行了非常详细的讲解,以下为定义:

1
2
3
4
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;

注意事项:

  1. 不要试图复制char *到data所指向的内存,可能导致coredump
  2. 初始化相关api都是基于常量字符串,char *类型慎用
  3. 如果非要复制char *到data所指向的内存,一定先分配内存

2.2.2 ngx_array_t

Nginx开发从入门到精通已经对ngx_array_t的定义以及相关api进行了非常详细的讲解,以下为定义:

1
2
3
4
5
6
7
8
typedef struct ngx_array_s       ngx_array_t;
struct ngx_array_s {
void *elts;
ngx_uint_t nelts;
size_t size;
ngx_uint_t nalloc;
ngx_pool_t *pool;
};

注意事项

  1. 如果使用ngx_array_t作为字符串容器,在调用ngx_array_create时,使用字符串长度最后一个参数
  2. ngx_array_push返回第n个元素首地址,要注意指针运算

3. handler模块的编写步骤

  1. 编写模块基本结构。包括模块的定义,模块上下文结构,模块的配置结构等,即分别初始化如下变量:
1
2
3
static ngx_command_t ngx_http_module_name_commands[] = {};
static ngx_http_module_t ngx_http_module_name_module_ctx = {};
ngx_module_t ngx_http_module_name_module = {};
  1. 实现handler的挂载函数。根据模块的需求选择正确的挂载方式,即实现如下函数:
1
static ngx_int_t ngx_http_module_name_init(ngx_conf_t *cf);
  1. 编写handler处理函数。模块的功能主要通过这个函数来完成,即实现如下函数:
1
static ngx_int_t ngx_http_module_name_handler(ngx_http_request_t *r);

3.1 模块编译

对于开发一个模块,我们是需要把这个模块的C代码组织到一个目录里,同时需要编写一个config文件。这个config文件的内容就是告诉nginx的编译脚本,该如何进行编译。

1
2
3
4
 
ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"

执行如下命令进行编译

1
./configure –add-module=/home/jizhao/open_source/book_module        

4. 部分功能简介

4.1 日志系统

Nginx的日志系统分为两个部分,一个是Nginx核心的错误日志(包括debug日志),一个是各模块的访问日志,整个Nginx框架中主要以核心的错误日志为主,各模块的访问日志根据模块调用顺序使用,下面分别介绍两种日志系统。

4.1.1 错误日志

关于错误日志的配置指令可以参考error_log,分析Nginx源码可以发现,源码中多处使用ngx_log_debug0,ngx_log_debug1等类似的宏定义,启用这些宏定义需要在configure的时候执行–with-debug。
错误日志初始化分为两个阶段,一个是ngx_log_init函数中初始化日志对象,另外一个就是在解析配置文件的时候会新建一个日志对象。

4.1.2 访问日志

目前实现了访问日志的官方模块有http模块,stream模块,其中http模块的配置指令可以参考ngx_http_log_module。http模块的日志系统是以http第三方模块的形式存在,所以编写的时候遵循第3节的规则,Nginx将HTTP请求划分为11个阶段,访问日志是最后一个阶段,所以访问日志在HTTP请求完成时写入。

4.2 upstream机制

upstream机制使得nginx能够作为代理服务器,或者负载均衡服务器,将来自客户端的请求转发到上游,并将上游响应转发到客户端。参考配置如下

#位于http上下文中
upstream local {
    server 127.0.0.1:8080 weight=5;
    server 127.0.0.1:80 weight=2;
}

4.3 反向代理

nginx反向代理实际上是一个upstream模块,实现upstream机制的几个回调函数。参考配置如下:

location / {
    proxy_pass http://local
}

5. 编码过程中遇到的问题

5.1 使用ngx_sprintf遇到的问题

ngx_sprintf函数用于向缓冲区中复制格式化字符串,跟标准C的sprintf函数一样,Nginx增加了一些格式,具体可以看ngx_string.c的源码,接下来结合代码说明一些问题:

1
2
3
4
5
6
7
         
size_t a;
off_t b;
u_char c[20] = {"0"};

ngx_sprintf(c, "a:%z,b:%O", a, b);

上面这段代码是正确示范,若a也为off_t类型,在执行ngx_sprintf时一定要使用%O,否者b的值会受影响。

6. 热更新

6.1 热更新步骤

  1. 替换nginx程序
  2. 向master进程发送信号 kill -USR2 PID,启动新程序
  3. 向旧master进程发送信号 kill -WINCH PID,告诉旧worker进程不再接受新请求

结语

此博文只粗略记录笔记,在后续会写详细的博客对nginx进行分析