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 | for option |
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 | extern ngx_module_t *ngx_modules[]; |
ngx_modules跟ngx_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 | typedef struct { |
注意事项:
- 不要试图复制char *到data所指向的内存,可能导致coredump
- 初始化相关api都是基于常量字符串,char *类型慎用
- 如果非要复制char *到data所指向的内存,一定先分配内存
2.2.2 ngx_array_t
Nginx开发从入门到精通已经对ngx_array_t的定义以及相关api进行了非常详细的讲解,以下为定义:
1 | typedef struct ngx_array_s ngx_array_t; |
注意事项
- 如果使用ngx_array_t作为字符串容器,在调用ngx_array_create时,使用字符串长度最后一个参数
- ngx_array_push返回第n个元素首地址,要注意指针运算
3. handler模块的编写步骤
- 编写模块基本结构。包括模块的定义,模块上下文结构,模块的配置结构等,即分别初始化如下变量:
1 | static ngx_command_t ngx_http_module_name_commands[] = {}; |
- 实现handler的挂载函数。根据模块的需求选择正确的挂载方式,即实现如下函数:
1 | static ngx_int_t ngx_http_module_name_init(ngx_conf_t *cf); |
- 编写handler处理函数。模块的功能主要通过这个函数来完成,即实现如下函数:
1 | static ngx_int_t ngx_http_module_name_handler(ngx_http_request_t *r); |
3.1 模块编译
对于开发一个模块,我们是需要把这个模块的C代码组织到一个目录里,同时需要编写一个config文件。这个config文件的内容就是告诉nginx的编译脚本,该如何进行编译。
1 |
|
执行如下命令进行编译
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 |
|
上面这段代码是正确示范,若a也为off_t类型,在执行ngx_sprintf时一定要使用%O,否者b的值会受影响。
6. 热更新
6.1 热更新步骤
- 替换nginx程序
- 向master进程发送信号 kill -USR2 PID,启动新程序
- 向旧master进程发送信号 kill -WINCH PID,告诉旧worker进程不再接受新请求
结语
此博文只粗略记录笔记,在后续会写详细的博客对nginx进行分析