Nginx是我们学习编程的一个非常有参考价值的开源项目。良好的编码风格,高效的数据结构、架构设计。
通常学习Nginx主要有以下两种情形:
1) 需求驱动型。在实际应用中,需要在Nginx的基础上,开发一些特定需求的模块,为此,我们需要去了解Nginx的工作原理、架构设计,并完成相关功能模 块的开发。这种情形下,一个比较好的学习路线是从开发一个简单的“Hello,world” HTTP模块入手,逐步深入。
2)知识驱动型。单纯从学习编程的角度出发,Nginx的模块化设计,架构,高效数据结构设计等都是不可多得的学习资源!这种情形下,通常从学习其常用的数据结构开始是一个不错的选择。
快课网在此搜罗了一些优质资源。从本文开始讲述Nginx中常用的数据结构,主要包括Nginx的数组结构、链表结构、队列、hash结构、内存池等。
0. 序
本文继续介绍nginx的数据结构——队列。Nginx的队列由有头节点的双向循环链表实现,具体可参看Linux内核链表实现。
链表实现文件:文件:./src/core/ngx_queue.h/.c。.表示nginx-1.0.4代码目录,本文为/usr/src/nginx-1.0.4。
1.队列结构
nginx的队列是由具有头节点的双向循环链表实现的,每一个节点结构为ngx_queue_t,定义如下。
1
2
3
4
5
|
typedef
struct
ngx_queue_s
ngx_queue_t
;
struct
ngx_queue_s
{
//队列结构
ngx_queue_t *
prev
;
ngx_queue_t *
next
;
}
;
|
其中,sizeof(ngx_queue_t)=8。
从队列结构定义可以看出,nginx的队列结构里并没有其节点的数据内容。
2. 队列操作
队列有如下操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
//初始化队列
ngx_queue_init
(
q
)
//判断队列是否为空
ngx_queue_empty
(
h
)
//在头节点之后插入新节点
ngx_queue_insert_head
(
h
,
x
)
//在尾节点之后插入新节点
ngx_queue_insert_tail
(
h
,
x
)
//删除节点x
ngx_queue_remove
(
x
)
//分割队列
ngx_queue_split
(
h
,
q
,
n
)
//链接队列
ngx_queue_add
(
h
,
n
)
//获取队列的中间节点
ngx_queue_t *
ngx_queue_middle
(
ngx_queue_t *
queue
)
//排序队列(稳定的插入排序)
void
ngx_queue_sort
(
ngx_queue_t *
queue
,
ngx_int_t
(
*
cmp
)
(
const
ngx_queue_t*
,
const
ngx_queue_t*
)
)
|
其中,插入节点、取队列头、取队列尾等操作由宏实现,获取中间节点、排序等操作由函数实现。下面简单介绍。
2.1 在头节点之后插入
在头节点之后插入操作由宏ngx_queue_insert_head完成,如下。
1
2
3
4
5
|
#define ngx_queue_insert_head(h, x) \
(
x
)
->
next
=
(
h
)
->
next
;
\
(
x
)
->
next
->
prev
=
x
;
\
(
x
)
->
prev
=
h
;
\
(
h
)
->
next
=
x
|
画出该操作的逻辑图,如下。
图中虚线表示被修改/删除的指针,蓝色表示新修改/增加的指针。下同。
2.2在尾节点之后插入
在尾节点之后插入操作由宏ngx_queue_insert_tail完成,如下。
1
2
3
4
5
|
#define ngx_queue_insert_tail(h, x) \
(
x
)
->
prev
=
(
h
)
->
prev
;
\
(
x
)
->
prev
->
next
=
x
;
\
(
x
)
->
next
=
h
;
\
(
h
)
->
prev
=
x
|
该操作的逻辑图如下。
2.3 删除节点
在尾节点之后插入操作由宏ngx_queue_remove完成,如下。
1
2
3
4
5
6
7
8
9
10
11
|
#if (NGX_DEBUG)
#define ngx_queue_remove(x) \
(
x
)
->
next
->
prev
=
(
x
)
->
prev
;
\
(
x
)
->
prev
->
next
=
(
x
)
->
next
;
\
(
x
)
->
prev
=
NULL
;
\
(
x
)
->
next
=
NULL
#else
#define ngx_queue_remove(x) \
(
x
)
->
next
->
prev
=
(
x
)
->
prev
;
\
(
x
)
->
prev
->
next
=
(
x
)
->
next
#endif
|
该操作的逻辑图如下。
2.4 分割队列
分割队列操作由宏ngx_queue_split完成,如下。
1
2
3
4
5
6
7
|
#define ngx_queue_split(h, q, n) \
(
n
)
->
prev
=
(
h
)
->
prev
;
\
(
n
)
->
prev
->
next
=
n
;
\
(
n
)
->
next
=
q
;
\
(
h
)
->
prev
=
(
q
)
->
prev
;
\
(
h
)
->
prev
->
next
=
h
;
\
(
q
)
->
prev
=
n
;
|
该宏有3个参数,h为队列头(即链表头指针),将该队列从q节点将队列(链表)分割为两个队列(链表),q之后的节点组成的新队列的头节点为n,图形演示如下。
2.5 链接队列
链接队列由宏ngx_queue_add完成,操作如下。
1
2
3
4
5
|
#define ngx_queue_add(h, n) \
(
h
)
->
prev
->
next
=
(
n
)
->
next
;
\
(
n
)
->
next
->
prev
=
(
h
)
->
prev
;
\
(
h
)
->
prev
=
(
n
)
->
prev
;
\
(
h
)
->
prev
->
next
=
h
;
|
其中,h、n分别为两个队列的指针,即头节点指针,该操作将n队列链接在h队列之后。演示图形如下。
宏ngx_queue_split和ngx_queue_add只在http模块locations相关操作中使用,在后续的讨论http模块locations相关操作时再详细叙述。
2.6 获取中间节点
中间节点,若队列有奇数个(除头节点外)节点,则返回中间的节点;若队列有偶数个节点,则返回后半个队列的第一个节点。操作如下。
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
|
/*
* find the middle queue element if the queue has odd number of elements
* or the first element of the queue’s second part otherwise
*/
ngx_queue_t *
ngx_queue_middle
(
ngx_queue_t *
queue
)
{
ngx_queue_t *
middle
,
*
next
;
middle
=
ngx_queue_head
(
queue
)
;
if
(
middle
==
ngx_queue_last
(
queue
)
)
{
return
middle
;
}
next
=
ngx_queue_head
(
queue
)
;
for
(
;
;
)
{
middle
=
ngx_queue_next
(
middle
)
;
next
=
ngx_queue_next
(
next
)
;
if
(
next
==
ngx_queue_last
(
queue
)
)
{
//偶数个节点,在此返回后半个队列的第一个节点
return
middle
;
}
next
=
ngx_queue_next
(
next
)
;
if
(
next
==
ngx_queue_last
(
queue
)
)
{
//奇数个节点,在此返回中间节点
return
middle
;
}
}
}
|
为什么该算法能够找到中间节点?
——注意代码中的next指针,其每次均会后移两个位置(节点),而middle指针每次后移一个位置(节点)。演示图形如下。
2.7 队列排序
队列排序采用的是稳定的简单插入排序方法,即从第一个节点开始遍历,依次将当前节点(q)插入前面已经排好序的队列(链表)中,下面程序中,前面已经排好序的队列的尾节点为prev。操作如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/* the stable insertion sort */
void
ngx_queue_sort
(
ngx_queue_t *
queue
,
ngx_int_t
(
*
cmp
)
(
const
ngx_queue_t *
,
const
ngx_queue_t *
)
)
{
ngx_queue_t *
q
,
*
prev
,
*
next
;
q
=
ngx_queue_head
(
queue
)
;
if
(
q
==
ngx_queue_last
(
queue
)
)
{
return
;
}
for
(
q
=
ngx_queue_next
(
q
)
;
q
!=
ngx_queue_sentinel
(
queue
)
;
q
=
next
)
{
prev
=
ngx_queue_prev
(
q
)
;
next
=
ngx_queue_next
(
q
)
;
ngx_queue_remove
(
q
)
;
do
{
if
(
cmp
(
prev
,
q
)
<=
0
)
{
//比较
break
;
}
prev
=
ngx_queue_prev
(
prev
)
;
//prev指针前移
}
while
(
prev
!=
ngx_queue_sentinel
(
queue
)
)
;
ngx_queue_insert_after
(
prev
,
q
)
;
//将q插入prev节点之后(此处即为简单插入)
}
}
|
该排序操作使用前面介绍的宏来完成其插入动作,只是一些简单的修改指针指向的操作,效率较高。关于该操作的例子,请参考后文的内容。
2.8 如何获取队列节点数据
由队列基本结构和以上操作可知,nginx的队列操作只对链表指针进行简单的修改指向操作,并不负责节点数据空间的分配。因此,用户在使用nginx队列时,要自己定义数据结构并分配空间,且在其中包含一个ngx_queue_t的指针或者对象,当需要获取队列节点数据时,使用ngx_queue_data宏,其定义如下。
1
2
|
#define ngx_queue_data(q, type, link) \
(
type *
)
(
(
u_char *
)
q
–
offsetof
(
type
,
link
)
)
|
由该宏定义可以看出,一般定义队列节点结构(该结构类型为type)时,需要将真正的数据放在前面,而ngx_queue_t结构放在后面,故该宏使用减法计算整个节点结构的起始地址(需要进行类型转换)。关于该宏的使用,请参考下文的代码。
3. 一个例子
本节给出一个创建内存池并从中分配队列头节点和其他节点组成队列的简单例子。在该例中,队列的数据是一系列的二维点(x,y分别表示该点的横、纵坐标),将这些点插入队列后进行排序,以此向读者展示nginx队列的使用方法。
3.1代码
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
145
146
147
148
149
150
151
152
|
/**
* ngx_queue_t test
*/
#include <stdio.h>
#include "ngx_config.h"
#include "ngx_conf_file.h"
#include "nginx.h"
#include "ngx_core.h"
#include "ngx_palloc.h"
#include "ngx_queue.h"
//2-dimensional point (x, y) queue structure
typedef
struct
{
int
x
;
int
y
;
}
my_point_t
;
typedef
struct
{
my_point_t
point
;
ngx_queue_t
queue
;
}
my_point_queue_t
;
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
,
.
.
.
)
{
}
void
dump_pool
(
ngx_pool_t*
pool
)
{
while
(
pool
)
{
printf
(
"pool = 0x%x\n"
,
pool
)
;
printf
(
" .d\n"
)
;
printf
(
" .last = 0x%x\n"
,
pool
->
d
.
last
)
;
printf
(
" .end = 0x%x\n"
,
pool
->
d
.
end
)
;
printf
(
" .next = 0x%x\n"
,
pool
->
d
.
next
)
;
printf
(
" .failed = %d\n"
,
pool
->
d
.
failed
)
;
printf
(
" .max = %d\n"
,
pool
->
max
)
;
printf
(
" .current = 0x%x\n"
,
pool
->
current
)
;
printf
(
" .chain = 0x%x\n"
,
pool
->
chain
)
;
printf
(
" .large = 0x%x\n"
,
pool
->
large
)
;
printf
(
" .cleanup = 0x%x\n"
,
pool
->
cleanup
)
;
printf
(
" .log = 0x%x\n"
,
pool
->
log
)
;
printf
(
"available pool memory = %d\n\n"
,
pool
->
d
.
end
-
pool
->
d
.
last
)
;
pool
=
pool
->
d
.
next
;
}
}
void
dump_queue_from_head
(
ngx_queue_t *
que
)
{
ngx_queue_t *
q
=
ngx_queue_head
(
que
)
;
printf
(
"(0x%x: (0x%x, 0x%x)) <==> \n"
,
que
,
que
->
prev
,
que
->
next
)
;
for
(
;
q
!=
ngx_queue_sentinel
(
que
)
;
q
=
ngx_queue_next
(
q
)
)
{
my_point_queue_t *
point
=
ngx_queue_data
(
q
,
my_point_queue_t
,
queue
)
;
printf
(
"(0x%x: (%-2d, %-2d), 0x%x: (0x%x, 0x%x)) <==> \n"
,
point
,
point
->
point
.
x
,
point
->
point
.
y
,
&
point
->
queue
,
point
->
queue
.
prev
,
point
->
queue
.
next
)
;
}
}
void
dump_queue_from_tail
(
ngx_queue_t *
que
)
{
ngx_queue_t *
q
=
ngx_queue_last
(
que
)
;
printf
(
"(0x%x: (0x%x, 0x%x)) <==> \n"
,
que
,
que
->
prev
,
que
->
next
)
;
for
(
;
q
!=
ngx_queue_sentinel
(
que
)
;
q
=
ngx_queue_prev
(
q
)
)
{
my_point_queue_t *
point
=
ngx_queue_data
(
q
,
my_point_queue_t
,
queue
)
;
printf
(
"(0x%x: (%-2d, %-2d), 0x%x: (0x%x, 0x%x)) <==> \n"
,
point
,
point
->
point
.
x
,
point
->
point
.
y
,
&
point
->
queue
,
point
->
queue
.
prev
,
point
->
queue
.
next
)
;
}
}
//sort from small to big
ngx_int_t
my_point_cmp
(
const
ngx_queue_t*
lhs
,
const
ngx_queue_t*
rhs
)
{
my_point_queue_t *
pt1
=
ngx_queue_data
(
lhs
,
my_point_queue_t
,
queue
)
;
my_point_queue_t *
pt2
=
ngx_queue_data
(
rhs
,
my_point_queue_t
,
queue
)
;
if
(
pt1
->
point
.
x
<
pt2
->
point
.
x
)
return
0
;
else
if
(
pt1
->
point
.
x
>
pt2
->
point
.
x
)
return
1
;
else
if
(
pt1
->
point
.
y
<
pt2
->
point
.
y
)
return
0
;
else
if
(
pt1
->
point
.
y
>
pt2
->
point
.
y
)
return
1
;
return
1
;
}
#define Max_Num 6
int
main
(
)
{
ngx_pool_t *
pool
;
ngx_queue_t *
myque
;
my_point_queue_t *
point
;
my_point_t
points
[
Max_Num
]
=
{
{
10
,
1
}
,
{
20
,
9
}
,
{
9
,
9
}
,
{
90
,
80
}
,
{
5
,
3
}
,
{
50
,
20
}
}
;
int
i
;
printf
(
"--------------------------------\n"
)
;
printf
(
"create a new pool:\n"
)
;
printf
(
"--------------------------------\n"
)
;
pool
=
ngx_create_pool
(
1024
,
NULL
)
;
dump_pool
(
pool
)
;
printf
(
"--------------------------------\n"
)
;
printf
(
"alloc a queue head and nodes :\n"
)
;
printf
(
"--------------------------------\n"
)
;
myque
=
ngx_palloc
(
pool
,
sizeof
(
ngx_queue_t
)
)
;
//alloc a queue head
ngx_queue_init
(
myque
)
;
//init the queue
//insert some points into the queue
for
(
i
=
0
;
i
<
Max_Num
;
i
++
)
{
point
=
(
my_point_queue_t*
)
ngx_palloc
(
pool
,
sizeof
(
my_point_queue_t
)
)
;
point
->
point
.
x
=
points
[
i
]
.
x
;
point
->
point
.
y
=
points
[
i
]
.
y
;
ngx_queue_init
(
&
point
->
queue
)
;
//insert this point into the points queue
ngx_queue_insert_head
(
myque
,
&
point
->
queue
)
;
}
dump_queue_from_tail
(
myque
)
;
printf
(
"\n"
)
;
printf
(
"--------------------------------\n"
)
;
printf
(
"sort the queue:\n"
)
;
printf
(
"--------------------------------\n"
)
;
ngx_queue_sort
(
myque
,
my_point_cmp
)
;
dump_queue_from_head
(
myque
)
;
printf
(
"\n"
)
;
printf
(
"--------------------------------\n"
)
;
printf
(
"the pool at the end:\n"
)
;
printf
(
"--------------------------------\n"
)
;
dump_pool
(
pool
)
;
ngx_destroy_pool
(
pool
)
;
return
0
;
}
|
3.2如何编译
本文编写的makefile文件如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
CXX
=
gcc
CXXFLAGS
+=
-
g
-
Wall
-
Wextra
NGX_ROOT
=
/
usr
/
src
/
nginx
-
1.0.4
TARGETS
=
ngx_queue_t_test
TARGETS_C_FILE
=
$
(
TARGETS
)
.
c
CLEANUP
=
rm
-
f
$
(
TARGETS
)
*
.
o
all
:
$
(
TARGETS
)
clean
:
$
(
CLEANUP
)
CORE_INCS
=
-
I
.
\
-
I
$
(
NGX_ROOT
)
/
src
/
core
\
-
I
$
(
NGX_ROOT
)
/
src
/
event
\
-
I
$
(
NGX_ROOT
)
/
src
/
event
/
modules
\
-
I
$
(
NGX_ROOT
)
/
src
/
os
/
unix
\
-
I
$
(
NGX_ROOT
)
/
objs
\
NGX_PALLOC
=
$
(
NGX_ROOT
)
/
objs
/
src
/
core
/
ngx_palloc
.
o
NGX_STRING
=
$
(
NGX_ROOT
)
/
objs
/
src
/
core
/
ngx_string
.
o
NGX_ALLOC
=
$
(
NGX_ROOT
)
/
objs
/
src
/
os
/
unix
/
ngx_alloc
.
o
NGX_QUEUE
=
$
(
NGX_ROOT
)
/
objs
/
src
/
core
/
ngx_queue
.
o
$
(
TARGETS
)
:
$
(
TARGETS_C_FILE
)
$
(
CXX
)
$
(
CXXFLAGS
)
$
(
CORE_INCS
)
$
(
NGX_PALLOC
)
$
(
NGX_STRING
)
$
(
NGX_ALLOC
)
$
(
NGX_QUEUE
)
$
^
-
o
$
@
|
3.3 运行结果
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
|
# ./ngx_queue_t_test
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
create
a
new
pool
:
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
pool
=
0x8bcf020
.
d
.
last
=
0x8bcf048
.
end
=
0x8bcf420
.
next
=
0x0
.
failed
=
0
.
max
=
984
.
current
=
0x8bcf020
.
chain
=
0x0
.
large
=
0x0
.
cleanup
=
0x0
.
log
=
0x0
available
pool
memory
=
984
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
alloc
a
queue
head
and
nodes
:
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
(
0x8bcf048
:
(
0x8bcf058
,
0x8bcf0a8
)
)
<=
=
>
(
0x8bcf050
:
(
10
,
1
)
,
0x8bcf058
:
(
0x8bcf068
,
0x8bcf048
)
)
<=
=
>
(
0x8bcf060
:
(
20
,
9
)
,
0x8bcf068
:
(
0x8bcf078
,
0x8bcf058
)
)
<=
=
>
(
0x8bcf070
:
(
9
,
9
)
,
0x8bcf078
:
(
0x8bcf088
,
0x8bcf068
)
)
<=
=
>
(
0x8bcf080
:
(
90
,
80
)
,
0x8bcf088
:
(
0x8bcf098
,
0x8bcf078
)
)
<=
=
>
(
0x8bcf090
:
(
5
,
3
)
,
0x8bcf098
:
(
0x8bcf0a8
,
0x8bcf088
)
)
<=
=
>
(
0x8bcf0a0
:
(
50
,
20
)
,
0x8bcf0a8
:
(
0x8bcf048
,
0x8bcf098
)
)
<=
=
>
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
sort
the
queue
:
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
(
0x8bcf048
:
(
0x8bcf088
,
0x8bcf098
)
)
<=
=
>
(
0x8bcf090
:
(
5
,
3
)
,
0x8bcf098
:
(
0x8bcf048
,
0x8bcf078
)
)
<=
=
>
(
0x8bcf070
:
(
9
,
9
)
,
0x8bcf078
:
(
0x8bcf098
,
0x8bcf058
)
)
<=
=
>
(
0x8bcf050
:
(
10
,
1
)
,
0x8bcf058
:
(
0x8bcf078
,
0x8bcf068
)
)
<=
=
>
(
0x8bcf060
:
(
20
,
9
)
,
0x8bcf068
:
(
0x8bcf058
,
0x8bcf0a8
)
)
<=
=
>
(
0x8bcf0a0
:
(
50
,
20
)
,
0x8bcf0a8
:
(
0x8bcf068
,
0x8bcf088
)
)
<=
=
>
(
0x8bcf080
:
(
90
,
80
)
,
0x8bcf088
:
(
0x8bcf0a8
,
0x8bcf048
)
)
<=
=
>
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
the
pool
at
the
end
:
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
pool
=
0x8bcf020
.
d
.
last
=
0x8bcf0b0
.
end
=
0x8bcf420
.
next
=
0x0
.
failed
=
0
.
max
=
984
.
current
=
0x8bcf020
.
chain
=
0x0
.
large
=
0x0
.
cleanup
=
0x0
.
log
=
0x0
available
pool
memory
=
880
|
该队列的逻辑图如下。
4. 小结
本文针对nginx-1.0.4的队列进行了较为全面的分析,包括队列结构和队列操作,队列操作主要包括在头节点之后插入节点、在尾节点之后插入节点、获取中间节点、队列排序等。最后通过一个简单例子向读者展示nginx队列的使用方法,同时借此向读者展示编译测试nginx代码的方法。
系列文章:
Nginx高效数据结构(1)——数组(ngx_array_t)
Nginx高效数据结构(2)——链表(ngx_list_t)
Nginx高效数据结构(3)——队列(ngx_queue_t)
Nginx高效数据结构(4)——Hash表(ngx_hash_t)
Nginx高效数据结构(5)——内存池(ngx_pool_t)
作者:阿波
EDITED BY:快课(www.cricode.com)
本文链接:http://cricode.com/2983.html
转载请保留原始作者信息及本文链接,谢谢!