虽然代码理解起来比较混乱,但是使用还是比较简单的,常用的有创建 hash 和在 hash 中进行查找两个操作,对于创建hash的操作,过程一般为:
- 构造一个 ngx_hash_key_t 为成员的数组, 包含 key, value 和 使用key计算出的一个hash值
- 构建一个 ngx_hash_init_t结构体的变量, 其中包含了ngx_hash_t 的成员, 为hash的结构体, 还包括一些其他初始设置,如bucket的大小,内存池等
- 调用 ngx_hash_init 传入 ngx_hash_init_t 结构, ngx_hash_key_t 的数组,和数组的长度, 进行初始化,这样 ngx_hash_init_t的hash成员就是我们要的hash结构
查找的过程很简单
- 计算 key 的hash值
- 使用 ngx_hash_find 进行查找,需要同时传入 hash值和key ,返回的就是value的指针
需要注意的是,nginx 的 hash 在查找时使用的是分桶后线性查找法,因此当分桶数确定时查找效率同其中的总 key-val 对数量成反比。
下面是一些demo代码(可以从svn中找到)
#include
<stdio.h>
#include
"ngx_config.h"
#include
"ngx_conf_file.h"
#include
"nginx.h"
#include
"ngx_core.h"
#include
"ngx_string.h"
#include
"ngx_palloc.h"
#include
"ngx_array.h"
#include
"ngx_hash.h"
volatile
ngx_cycle_t
*
ngx_cycle
;
void
ngx_log_error_core
(
ngx_uint_t level
,
ngx_log_t
*
log
,
ngx_err_t err
,
const
char
*
fmt
,
...)
{
}
static
ngx_str_t names
[]
=
{
ngx_string
(
"rainx"
),
ngx_string
(
"xiaozhe"
),
ngx_string
(
"zhoujian"
)};
static
char
*
descs
[]
=
{
"rainx's id is 1"
,
"xiaozhe's id is 2"
,
"zhoujian's id is 3"
};
// hash table的一些基本操作
int
main
()
{
ngx_uint_t k
;
//, p, h;
ngx_pool_t
*
pool
;
ngx_hash_init_t hash_init
;
ngx_hash_t
*
hash
;
ngx_array_t
*
elements
;
ngx_hash_key_t
*
arr_node
;
char
*
find
;
int
i
;
ngx_cacheline_size
=
32
;
// hash key cal start
ngx_str_t str
=
ngx_string
(
"hello, world"
);
k
=
ngx_hash_key_lc
(
str
.
data
,
str
.
len
);
pool
=
ngx_create_pool
(
1024
*
10
,
NULL
);
printf
(
"caculated key is %u \n"
,
k
);
// hask key cal end
//
hash
=
(
ngx_hash_t
*)
ngx_pcalloc
(
pool
,
sizeof
(
hash
));
hash_init
.
hash
=
hash
;
// hash结构
hash_init
.
key
=
&
ngx_hash_key_lc
;
// hash算法函数
hash_init
.
max_size
=
1024
*
10
;
// max_size
hash_init
.
bucket_size
=
64
;
// ngx_align(64, ngx_cacheline_size);
hash_init
.
name
=
"yahoo_guy_hash"
;
// 在log里会用到
hash_init
.
pool
=
pool
;
// 内存池
hash_init
.
temp_pool
=
NULL
;
// 创建数组
elements
=
ngx_array_create
(
pool
,
32
,
sizeof
(
ngx_hash_key_t
));
for
(
i
=
0
;
i
<
3
;
i
++)
{
arr_node
=
(
ngx_hash_key_t
*)
ngx_array_push
(
elements
);
arr_node
->
key
=
(
names
[
i
]);
arr_node
->
key_hash
=
ngx_hash_key_lc
(
arr_node
->
key
.
data
,
arr_node
->
key
.
len
);
arr_node
->
value
=
(
void
*)
descs
[
i
];
//
printf
(
"key: %s , key_hash: %u\n"
,
arr_node
->
key
.
data
,
arr_node
->
key_hash
);
}
if
(
ngx_hash_init
(&
hash_init
,
(
ngx_hash_key_t
*)
elements
->
elts
,
elements
->
nelts
)!=
NGX_OK
){
return
1
;
}
// 查找
k
=
ngx_hash_key_lc
(
names
[
0
].
data
,
names
[
0
].
len
);
printf
(
"%s key is %d\n"
,
names
[
0
].
data
,
k
);
find
=
(
char
*)
ngx_hash_find
(
hash
,
k
,
(
u_char
*)
names
[
0
].
data
,
names
[
0
].
len
);
if
(
find
)
{
printf
(
"get desc of rainx: %s\n"
,
(
char
*)
find
);
}
ngx_array_destroy
(
elements
);
ngx_destroy_pool
(
pool
);
return
0
;
}
运行结果
rainx@rainx
-
laptop
:~/
land
/
nginxsrp
/
src
/
demo
/
basic_types$
./
hash_op
caculated key
is
3654358412
key
:
rainx
,
key_hash
:
108275556
key
:
xiaozhe
,
key_hash
:
2225329080
key
:
zhoujian
,
key_hash
:
3269715264
rainx key
is
108275556
get
desc of rainx
:
rainx
's id is 1
ngx_list
ngx_list 的结构并不复杂,ngx为我们封装了ngx_list_create, ngx_list_init, 和 ngx_list_push等(建立,初始化,添加)操作, 但是对于我们来说最常用的是遍历操作, 下面是nginx的注释里面提到的遍历的例子
part
=
&
list
.
part
;
data
=
part
->
elts
;
for
(
i
=
0
;;
i
++)
{
if
(
i
>=
part
->
nelts
)
{
if
(
part
->
next
==
NULL
)
{
break
;
}
part
=
part
->
next
;
data
=
part
->
elts
;
i
=
0
;
}
...
data
[
i
]
...
}
了解nginx的core module 的结构和运行机制
参考资料
在开始这个task的学习的时候,经过搜索发现了langwan同学之前对nginx的源代码研究资料,很有参考意义,所以大量节省了我们的工作,我觉得对于本章的进行比较有用的是,下面这几个文章
- nginx源代码分析 http://hi.baidu.com/langwan/blog/item/6b18ef24cd859e064c088d28.html
- nginx 缓冲区构造 http://hi.baidu.com/langwan/blog/item/822b758d5d1d9a1ab31bbaf8.html
- Nginx源代码分析 - 日志处理 http://hi.baidu.com/langwan/blog/item/7e7db51978e04e4d43a9ad32.html
Debug信息的输出
为了方便研究,将nginx的debug 信息打开,重新编译
rainx@rainx
-
laptop
:~/
land
/
nginx
-
0.7
.
61
$
./
configure
--
prefix
=
/home/
rainx
/
land
/
test
--
with
-
debug
然后修改nginx.conf
worker_processes
2
;
error_log logs
/
error
.
log debug
;
打开debug信息的支持,并使用2个worker进程,通过查看 log 信息来了解 nginx 运行的情况
基于上面的配置信息,结合一个简单的http访问操作,我这里记录了一个 log日志的例子
ngx_init_cycle
其中一个比较重要的函数调用是, ngx_init_cycle, 这个是使用kscope输出的他的调用关系,他被main, ngx_master_process_cycle,ngx_single_process_cycle 调用, 其中后两者是在reconfigure的时候被调用的
他主要做了如下几件事情:
初始化
cycle
是基于旧有的
cycle
进行的,比如这里的
init_cycle
,会继承
old cycle
的很多属性,
比如
log
等,
但是同时会对很多资源重新分配,比如
pool
,
shared mem
,
file handler
,
listening socket
等,同时清除旧有的
cycle
的资源
另外,ngx_master/single_process_cycle 里面会对init_process进行调用, 并且循环调用 ngx_process_events_and_timers , 其中里面会调用ngx_process_events(cycle, timer, flags); 对事件循环进行polliing 时间一般默认为 500 ms
了解nginx的http core module 的结构和运行机制
HTTP相关的Module都在 src/http 目录和其子目录下, 其中 src/http 下的文件为http模块的核心文件, src/http/modules 下的文件为http模块的扩展模块。
其中:
ngx_http.[c|h]
ngx_http.c 中,注册了 http 这个指令的处理模块,对应ngx_http_block函数
static
ngx_command_t ngx_http_commands
[]
=
{
{
ngx_string
(
"http"
),
NGX_MAIN_CONF
|
NGX_CONF_BLOCK
|
NGX_CONF_NOARGS
,
ngx_http_block
,
0
,
0
,
NULL
},
ngx_null_command
};
这个函数里面会进行一些conf资源分配/Merge,配置文件解析等工作。 这里面有个一比较重要的工作是注册了nginx http 的 phase handler
if
(
ngx_http_init_phase_handlers
(
cf
,
cmcf
)
!=
NGX_OK
)
{
return
NGX_CONF_ERROR
;
}
phase handler的类型在 ngx_http_core_module 这里定义:
typedef
enum
{
NGX_HTTP_POST_READ_PHASE
=
0
,
NGX_HTTP_SERVER_REWRITE_PHASE
,
NGX_HTTP_FIND_CONFIG_PHASE
,
NGX_HTTP_REWRITE_PHASE
,
NGX_HTTP_POST_REWRITE_PHASE
,
NGX_HTTP_PREACCESS_PHASE
,
NGX_HTTP_ACCESS_PHASE
,
NGX_HTTP_POST_ACCESS_PHASE
,
NGX_HTTP_TRY_FILES_PHASE
,
NGX_HTTP_CONTENT_PHASE
,
NGX_HTTP_LOG_PHASE
}
ngx_http_phases
;
每一个phase的handlers 都是一个数组,里面可以包含多个元素,通过 ngx_array_push 添加新的handler
其中每个phase的处理大都包含了对ngx_request_t 的 write 或者 read event的改写,其中
在 ngx_http_core_content_phase 里面, 有对location handler的调用, 其中的 r->content_handler 就是运行时刻从location handler中注册的,
if
(
r
->
content_handler
)
{
r
->
write_event_handler
=
ngx_http_request_empty_handler
;
ngx_http_finalize_request
(
r
,
r
->
content_handler
(
r
));
/*实际的请求发送处理*/
return
NGX_OK
;
}
其中, 在各个phase的结束阶段,一般都是调用
r
->
phase_handler
++;
return
NGX_AGAIN
;
移动request 中 phase_handler的指针,并且示意主程序继续进行。
这里,无论是phase handler,还是 location handler,我们都是可以在程序里进行注册的。
另外, ngx_http_block 里面调用了 ngx_http_optimize_servers ,这个函数对listening和connection相关的变量进行了初始化和调优,并最终在 ngx_http_add_listening (被ngx_http_add_listening调用) 中注册了listening 的 handler 为 ngx_http_init_connection
ls
->
handler
=
ngx_http_init_connection
;
ngx_http_init_connection 在 ngx_http_request.c中定义,后续会进行详细的介绍
ngx_http_request.[c|h]
这里面,ngx_http_init_connection 注册了connection事件的读操作的回叫函数, 并将写操作设置为空函数
rev
=
c
->
read
;
rev
->
handler
=
ngx_http_init_request
;
c
->
write
->
handler
=
ngx_http_empty_handler
;
当新的连接进入的时候,就执行到 ngx_http_init_request, 开始对后面的流程进行处理,主要是将rev的handler 设置为ngx_http_process_request_line , 然后ngx_http_process_request_line 会先后有调度到 ngx_http_process_request_headers 和 ngx_http_process_request 函数对读取过来的event进行处理,其中, ngx_http_process_request_headers 里面会对http的请求头进行解析,ngx_http_process_request 设置event handler 到ngx_http_request_handler ,ngx_http_request_handler 中会根据事件可能是读取还是写入的操作分别调用 request 的 read_event_handler 和 write_event_handler ,所以后续程序对 request 的 read/write event_handler 调整 本质上类似对 rev 和 wev的handler的调整,只是回叫函数的参数变更为了 ngx_request_t 而不是之前的ngx_event_t
c
->
read
->
handler
=
ngx_http_request_handler
;
c
->
write
->
handler
=
ngx_http_request_handler
;
r
->
read_event_handler
=
ngx_http_block_reading
;
根据上面代码可以看出, 模块开始使用 ngx_http_block_reading 这个handler对后续的读请求进行处理
在注册完事件后, ngx_http_process_request 会分别调用下面的两个函数
ngx_http_handler
(
r
);
ngx_http_run_posted_requests
(
c
);
其中, ngx_http_handler 在ngx_http_core_module中定义,处理程序的主请求, ngx_http_run_posted_requests 在ngx_http_request.c 里定义,处理所有提交的子请求数据的输出。
ngx_http_core_module.[c|h]
对于 ngx_http_core_module 是http 模块中比较重要的模块, 他本身是一个 NGX_HTTP_MODULE (不同于ngx_http_module, ngx_http_module本质上是一个 NGX_CORE_MODULE
这里面对http block下面的一些指令进行了处理, 比如 server, location 等, 同时, 上面提到的 ngx_http_handler 也在这里面
ngx_http_handler 所作的最核心的工作就是在最后调用 并将 write event 设置为 ngx_http_core_run_phases, 开始依次处理各个阶段的 handler
当handler处理完成后,http的处理流程也就基本上完成了..
while
(
ph
[
r
->
phase_handler
].
checker
)
{
rc
=
ph
[
r
->
phase_handler
].
checker
(
r
,
&
ph
[
r
->
phase_handler
]);
if
(
rc
==
NGX_OK
)
{
return
;
}
}
run_phases 的过程实际上非常简单, 一次的运行每一个handler, 当任意一个handler返回ok或者所有handler执行完成后,整个流程结束。
这里需要注意的是, ph的下标变化是根据 r->phase_handler 变量决定的, 所以在每个handler内部,如果想要让主程序继续处理下一个 handler,需要手动的 r->phase_handler++ ,将phase handler数组的下标转移到下一个成员。

