文章目录
- Nginx源码学习
- 基本数据结构
- 1、字符串结构:ngx_str_t
- 2、类似资源管理的结构:ngx_pool_t
- 3、Nginx数组结构:ngx_array_t
- 4、哈希表结构:
- (1) ngx_hash_t:普通哈希表
- (2) ngx_hash_wildcard_t:通配符域名哈希表
- (3) ngx_hash_combined_t:组合类型哈希表
- (4) ngx_hash_keys_arrays_t:该结构用来预处理key
- 5、响应消息结构:ngx_chain_t
- ngx_buf_t
- 6、链表结构:ngx_list_t
- 7、双向链表:ngx_queue_t
Nginx源码学习
- Nginx根据自己的特点实现了很多较为高效的数据结构和公共函数,所以在我们在开发的时候应该尽量调用Nginx提供的API
基本数据结构
1、字符串结构:ngx_str_t
- 位置:src/core 下 ngx_string.h 和 ngx_string.c
- 原型:可以看到,Nginx自定义了一个带有长度的字符串结构。这意味着,data所指向的字符串并不是以 “\0” 结束的。
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
- Nginx字符串操作宏:
- 1. 构造常量字符串:
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
- 2. 初始化字符串为空字符串:
#define ngx_null_string { 0, NULL }
- 3. 将ngx_str_t设置为text:
#define ngx_str_set(str, text) \
(str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
- 4. 将ngx_str_t设置为null:
#define ngx_str_null(str) (str)->len = 0; (str)->data = NULL
- Nginx字符串操作宏的使用:
ngx_str_t str = ngx_string("hello world");
ngx_str_t str1 = ngx_null_string;
ngx_str_t str2, str3;
ngx_str_set(&str2, "hello world");
ngx_str_null(&str3);
- Nginx字符串操作函数
- 1. 字符串转换:将src的前n个字符转换成小写存放到dst字符串中。str不会改变。
void ngx_strlow(u_char *dst, u_char *src, size_t n);
// 若想改变源字符串
ngx_strlow(str->data, str->data, str->len);
- 2. 字符串比较:
ngx_strncmp(s1, s2, n); // 区分大小写的比较前n个字符。
ngx_strcmp(s1, s2); // 区分大小写比较两个字符串。
ngx_int_t ngx_strcasecmp(u_char *s1, u_char *s2); // 不区分大小写的字符串比较。返回0表示相等。
ngx_int_t ngx_strncasecmp(u_char *s1, u_char *s2, size_t n); // 不区分大小写的前n个字符的比较。
- 3. 字符串格式化:
u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);
u_char * ngx_cdecl ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...); // 推荐使用
u_char * ngx_cdecl ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...); // 推荐使用
// 这里特别要提醒的是,我们最常用于格式化ngx_str_t结构,其对应的转义符是%V,传给函数的一定要是指针类型。
ngx_str_t str = ngx_string("hello world");
char buffer[1024];
ngx_snprintf(buffer, 1024, "%V", &str); // 注意,str取地址
- 关于Nginx中格式化参数在ngx_string.c中有说明
- 4. 字符串BASE64编解码:将结果放到dst中,需要保证dst有足够的空间存放结果。
// 如果不知道具体大小,可先调用ngx_base64_encoded_length与ngx_base64_decoded_length来预估最大占用空间。
#define ngx_base64_encoded_length(len) (((len + 2) / 3) * 4)
#define ngx_base64_decoded_length(len) (((len + 3) / 4) * 3)
void ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src);
ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);
- 5. 字符串ESCAPE编码:对src按照type方式编码,结果放到dst中。如果dst传NULL可获得结果的空间大小。
// type可以是:
#define NGX_ESCAPE_URI 0
#define NGX_ESCAPE_ARGS 1
#define NGX_ESCAPE_HTML 2
#define NGX_ESCAPE_REFRESH 3
#define NGX_ESCAPE_MEMCACHED 4
#define NGX_ESCAPE_MAIL_AUTH 5
uintptr_t ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type);
// 反escape编码:
// type可以是 0 或下面的宏:
#define NGX_UNESCAPE_URI 1
#define NGX_UNESCAPE_REDIRECT 2
void ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type);
- 如果type是0,则表示src中的所有字符都要进行转码。如果是NGX_UNESCAPE_URI与NGX_UNESCAPE_REDIRECT,则遇到’?’后就结束了,后面的字符就不管了。而NGX_UNESCAPE_URI与NGX_UNESCAPE_REDIRECT之间的区别是NGX_UNESCAPE_URI对于遇到的需要转码的字符,都会转码,而NGX_UNESCAPE_REDIRECT则只会对非可见字符进行转码。
- 6. 对html标签进行编码:dst传NULL返回结果占用空间大小。
uintptr_t ngx_escape_html(u_char *dst, u_char *src, size_t size);
- 7. 对JSON进行编码:dst传NULL返回结果占用空间大小。
uintptr_t ngx_escape_json(u_char *dst, u_char *src, size_t size);
2、类似资源管理的结构:ngx_pool_t
- 位置:src/core 下 ngx_palloc.h 和 ngx_palloc.c
- 原型:
typedef struct ngx_pool_s ngx_pool_t;
struct ngx_pool_s {
ngx_pool_data_t d;
size_t max;
ngx_pool_t *current;
ngx_chain_t *chain;
ngx_pool_large_t *large;
ngx_pool_cleanup_t *cleanup;
ngx_log_t *log;
};
- 操作:
- 1. 创建一个初始节点大小为size的pool:log为后续在该pool上进行操作时输出日志的对象。
// size的有效最大值为NGX_MAX_ALLOC_FROM_POOL,若传入的值超过NGX_MAX_ALLOC_FROM_POOL,
// 则实际用到的不会超过NGX_MAX_ALLOC_FROM_POOL,造成浪费。
// size还必须大于sizeof(ngx_pool_t),否则会导致程序崩溃,因为在这片内存中需要存储ngx_pool_t本身。
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
- 2. 内存分配:
// 从这个pool中分配一块为size大小的内存。此函数分配的内存的起始地址按照NGX_ALIGNMENT进行了对齐。
// 对齐操作会提高系统处理的速度,但会造成少量内存的浪费。
void *ngx_palloc(ngx_pool_t *pool, size_t size);
// 从这个pool中分配一块为size大小的内存。但是此函数分配的内存并没有像上面的函数那样进行对齐。
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
// 该函数也是分配size大小的内存,并且对分配的内存块进行了清零。内部实际上是调用ngx_palloc实现的。
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
// 按照指定对齐大小alignment来申请一块大小为size的内存。
// 此处获取的内存不管大小都将被置于大内存块链中管理。(也就是pool->large管理的链表)
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
- 3. 内存释放:
// 对于被置于大块内存链,也就是被large字段管理的一列内存中的某块进行释放。(也就是释放pool->large所管理链表的某个节点)
// 该函数的实现是顺序遍历large管理的大块内存链表。所以效率比较低下。
// 如果在这个链表中找到了这块内存,则释放,并返回NGX_OK。否则返回NGX_DECLINED。
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
// ngx_pool_t中的cleanup字段管理着一个特殊的链表,该链表的每一项都记录着一个特殊的需要释放的资源。
// 对于这个链表中每个节点所包含的资源如何去释放,是自说明的。
// 这也就提供了非常大的灵活性。意味着,ngx_pool_t不仅仅可以管理内存,通过这个机制,也可以管理任何需要释放的资源。
// 这个 size 就是要存储这个data字段所指向的资源的大小,该函数会为data分配size大小的空间。
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
// 看一下 ngx_pool_cleanup_t 结构
typedef void (*ngx_pool_cleanup_pt)(void *data);
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 是一个函数指针,指向一个可以释放data所对应资源的函数。该函数只有一个参数,就是data。
void *data; // 指明了该节点所对应的资源。
ngx_pool_cleanup_t *next; // 指向该链表中下一个元素。
};
// 该函数就是释放pool中持有的所有内存,以及依次调用cleanup字段所管理的链表中每个元素的handler字段所指向的函数。
// 释放掉所有该pool管理的资源。并且把pool指向的ngx_pool_t也释放掉。
void ngx_destroy_pool(ngx_pool_t *pool);
// 该函数释放pool中所有大块内存链表上的内存,小块内存链上的内存块都修改为可用。但是不会去处理cleanup链表上的结点。
void ngx_reset_pool(ngx_pool_t *pool);
3、Nginx数组结构:ngx_array_t
- 位置:src/core 下 ngx_array.c 和 ngx_array.h
- 原型:
typedef struct {
void *elts; // 指向实际的数据存储区域。
ngx_uint_t nelts; // 数组实际元素个数。
size_t size; // 数组单个元素的大小,单位是字节。
ngx_uint_t nalloc; /* 数组的容量。表示该数组在不引发扩容的前提下,可以最多存储的元素的个数。
当nelts增长到达nalloc 时,如果再往此数组中存储元素,则会引发数组的扩容。
数组的容量将会扩展到原有容量的2倍大小。实际上是分配新的一块内存,
新的一块内存的大小是原有内存大小的2倍。原有的数据会被拷贝到新的一块内存中。*/
ngx_pool_t *pool; // 该数组用来分配内存的内存池。
} ngx_array_t;
- 操作:
- 1. 创建数组对象:
// p:数组分配内存使用的内存池;
// n:数组的初始容量大小,即在不扩容的情况下最多可以容纳的元素个数。
// size:单个元素的大小,单位是字节。
ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
- 2. 添加新元素:在数组a上新追加一个元素,并返回指向新元素的指针。
// 需要把返回的指针使用类型转换,转换为具体的类型,然后再给新元素本身或者是各字段(如果数组的元素是复杂类型)赋值。
void *ngx_array_push(ngx_array_t *a);
- 3. 批量添加元素:在数组a上追加n个元素,并返回指向这些追加元素的首个元素的位置的指针。
void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);
- 4. 销毁数组对象:
// 销毁该数组对象,并将其内存放回内存池。
void ngx_array_destroy(ngx_array_t *a);
- 5. 初始化:
// 如果一个数组对象是被分配在栈上的,那么就需要调用此函数,进行初始化的工作以后,才可以使用。
// 由于使用ngx_palloc分配内存,数组在扩容时,旧的内存不会被释放,会造成内存的浪费。(ngx_array_push和ngx_array_push_n函数对扩容机制的实现)
// 因此,最好能提前规划好数组的容量,在创建或者初始化的时候一次搞定,避免多次扩容,造成内存浪费。
static ngx_inline ngx_int_t ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size);
4、哈希表结构:
(1) ngx_hash_t:普通哈希表
- 位置:src/core 下 ngx_hash.h 和 ngx_hash.c
- 原型:
typedef struct {
ngx_hash_elt_t **buckets;
ngx_uint_t size;
} ngx_hash_t;
- Nginx哈希表的特点:
- ngx_hash_t解决哈希冲突的方式类似开链法。
- ngx_hash_t不像其他的hash表的实现,可以插入删除元素,它只能一次初始化,就构建起整个hash表以后,既不能再删除,也不能在插入元素了。
- ngx_hash_t的开链并不是真的开了一个链表,实际上是开了一段连续的存储空间,几乎可以看做是一个数组。这是因为ngx_hash_t在初始化的时候,会经历一次预计算的过程,提前把每个桶里面会有多少元素放进去给计算出来,这样就提前知道每个桶的大小了。那么就不需要使用链表,一段连续的存储空间就足够了。这也从一定程度上节省了内存的使用。
- 操作:
- 1. 初始化函数:
// hinit是初始化的一些参数的一个集合。
// names是初始化一个 ngx_hash_t 所需要的所有 key 的一个数组。
// nelts就是key的个数。
ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);
// 看一下 ngx_hash_init_t 结构
typedef struct {
ngx_hash_t *hash; // 该字段如果为NULL,那么调用完初始化函数后,该字段指向新创建出来的hash表。
// 如果该字段不为NULL,那么在初始化的时候,所有的数据被插入到这个字段所指的hash表中。
ngx_hash_key_pt key; // 指向从字符串生成hash值的hash函数。nginx的源代码中提供了默认的实现函数ngx_hash_key_lc。
ngx_uint_t max_size; // hash表中的桶的个数。
// 该字段越大,元素存储时冲突的可能性越小,每个桶中存储的元素会更少,则查询起来的速度更快。
// 当然,这个值越大,越造成内存的浪费也越大。
ngx_uint_t bucket_size; // 每个桶的最大限制大小,单位是字节。
char *name; // 该 hash 表的名字。
ngx_pool_t *pool; // 该hash表分配内存使用的pool。
ngx_pool_t *temp_pool; // 该hash表使用的临时pool,在初始化完成以后,该pool可以被释放和销毁掉。
} ngx_hash_init_t;
// 再看一下 ngx_hash_key_t 结构
typedef struct {
ngx_str_t key;
ngx_uint_t key_hash; // key的 hash 值
void *value;
} ngx_hash_key_t;
- 2. 查找:
// 在hash里面查找key对应的value。
// 实际上这里的key是对真正的key(也就是name)计算出的hash值。
// len是name的长度。
// 如果查找成功,则返回指向value的指针,否则返回NULL。
void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);
(2) ngx_hash_wildcard_t:通配符域名哈希表
- 位置:src/core 下 ngx_hash.c 和 ngx_hash.h
- 原型:
typedef struct {
ngx_hash_t hash;
void *value;
} ngx_hash_wildcard_t;
- 特点:
- nginx为了处理带有通配符的域名的匹配问题,实现了ngx_hash_wildcard_t这样的hash表。
- 他可以支持两种类型的带有通配符的域名。
- 通配符在前的,例如:“*.abc.com”,也可以省略掉星号,直接写成”.abc.com”。
- 另外一种是通配符在末尾的,例如:“mail.xxx.*”,需要特别注意通配符在末尾的不可以被省略掉。
- 注意:
- 一个ngx_hash_wildcard_t类型的hash表只能包含通配符在前的key或者是通配符在后的key。不能同时包含两种类型通配符的key。
- 操作:
- 1. 初始化:
// 该函数迎来构建一个可以包含通配符key的hash表。该函数执行成功返回NGX_OK,否则NGX_ERROR。
// hinit: 构造一个通配符hash表所需参数的一个集合。
// nelts: names数组元素的个数。
ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);
- 对names参数的说明:
- 构造此hash表的所有的通配符key的数组。需要注意这里的key都是已经被预处理过的。例如:“*.abc.com”或者“.abc.com”被预处理完成以后,变成了“com.abc.”。而“mail.xxx.*”则被预处理为“mail.xxx.”。
为什么会被处理这样?这里不得不简单地描述一下通配符hash表的实现原理。
当构造此类型的hash表的时候,实际上是构造了一个hash表的一个“链表”,是通过hash表中的key“链接”起来的。比如:对于“*.abc.com”将会构造出2个hash表,第一个hash表中有一个key为com的表项,该表项的value包含有指向第二个hash表的指针,而第二个hash表中有一个表项abc,该表项的value包含有指向*.abc.com对应的value的指针。那么查询的时候,比如查询www.abc.com的时候,先查com,通过查com可以找到第二级的hash表,在第二级hash表中,再查找abc,依次类推,直到在某一级的hash表中查到的表项对应的value对应一个真正的值而非一个指向下一级hash表的指针的时候,查询过程结束。
这里有一点需要特别注意的是names数组中元素的value值低两位bit必须为0(有特殊用途)。如果不满足这个条件,这个hash表查询不出正确结果。
- 2. 查询:
// 该函数查询包含通配符在前的key的hash表的。
void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);
// 该函数查询包含通配符在末尾的key的hash表的。
void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);
// hwc: hash表对象的指针。
// name: 需要查询的域名,例如: www.abc.com。
// len: name的长度。
(3) ngx_hash_combined_t:组合类型哈希表
- 位置:src/core 下 ngx_hash.h 和 ngx_hash.c
- 原型:
// 该类型实际上包含了三个hash表,一个普通hash表,一个包含前向通配符的hash表和一个包含后向通配符的hash表。
typedef struct {
ngx_hash_t hash;
ngx_hash_wildcard_t *wc_head;
ngx_hash_wildcard_t *wc_tail;
} ngx_hash_combined_t;
- 操作:
- 查询:
// 该函数在此组合hash表中,依次查询其三个子hash表,看是否匹配,一旦找到,立即返回查找结果,
// 也就是说如果有多个可能匹配,则只返回第一个匹配的结果。
// 成功返回查询的结果,未查到则返回NULL。
void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name, size_t len);
// hash: 此组合hash表对象。
// key: 根据key计算出的hash值。
// name: key实际的具体内容。
// len: name的长度。
(4) ngx_hash_keys_arrays_t:该结构用来预处理key
- 位置:src/core 下 ngx_hash.h 和 ngx_hash.c
- 原型:
可以看到在构建一个ngx_hash_wildcard_t的时候,需要对通配符的哪些key进行预处理。
这个处理起来比较麻烦。而当有一组key,这些里面既有无通配符的key,也有包含通配符的key的时候。
我们就需要构建三个hash表,一个包含普通的key的hash表,一个包含前向通配符的hash表,
一个包含后向通配符的hash表(或者也可以把这三个hash表组合成一个ngx_hash_combined_t)。
在这种情况下,为了让大家方便的构造这些hash表,nginx提供给了此辅助类型。
typedef struct {
ngx_uint_t hsize; // 将要构建的hash表的桶的个数。对于使用这个结构中包含的信息构建的三种类型的hash表都会使用此参数。
ngx_pool_t *pool; // 构建这些hash表使用的pool。
ngx_pool_t *temp_pool;// 在构建这个类型以及最终的三个hash表过程中可能用到临时pool。该temp_pool可以在构建完成以后,被销毁掉。
ngx_array_t keys; // 存放所有非通配符key的数组。
ngx_array_t *keys_hash;// 这是个二维数组,第一个维度代表的是bucket的编号,
// 那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key。
/* 假设有3个key,分别是key1,key2和key3假设hash值算出来以后对hsize取模的值都是i,
那么这三个key的值就顺序存放在keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。
该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复。*/
ngx_array_t dns_wc_head; // 放前向通配符key被处理完成以后的值。
// 比如:“*.abc.com” 被处理完成以后,变成 “com.abc.” 被存放在此数组中。
ngx_array_t *dns_wc_head_hash; // 同理 keys_hash
ngx_array_t dns_wc_tail; // 存放后向通配符key被处理完成以后的值。
// 比如:“mail.xxx.*” 被处理完成以后,变成 “mail.xxx.” 被存放在此数组中。
ngx_array_t *dns_wc_tail_hash; // 同理 keys_hash
} ngx_hash_keys_arrays_t;
- 操作:
- 初始化:
// 初始化ngx_hash_keys_arrays_t结构,主要是对这个结构中的ngx_array_t类型的字段进行初始化,成功返回NGX_OK。
ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type);
// type: 该字段有2个值可选择,即 NGX_HASH_SMALL 和 NGX_HASH_LARGE 用来指明将要建立的hash表的类型,
// 如果是 NGX_HASH_SMALL,则有比较小的桶的个数和数组元素大小。NGX_HASH_LARGE则相反。
- 添加:有关于这个数据结构的使用,可以参考src/http/ngx_http.c中的ngx_http_server_names函数。
// 一般是循环调用这个函数,把一组键值对加入到这个结构体中。返回NGX_OK是加入成功。返回NGX_BUSY意味着key值重复。
ngx_int_t ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value, ngx_uint_t flags);
/* flags: 有两个标志位可以设置, NGX_HASH_WILDCARD_KEY 和 NGX_HASH_READONLY_KEY 。
同时要设置的使用按位或操作符就可以了。NGX_HASH_READONLY_KEY被设置的时候,在计算hash值的时候,key的值不会被转成小写字符,否则会。
NGX_HASH_WILDCARD_KEY被设置的时候,说明key里面可能含有通配符,会进行相应的处理。
如果两个标志位都不设置,传0。*/
5、响应消息结构:ngx_chain_t
- 位置:src/core 下 ngx_buf.h 和 ngx_buf.c
- 原型:
typedef struct ngx_chain_s ngx_chain_t;
struct ngx_chain_s {
ngx_buf_t *buf; // 实际数据。
ngx_chain_t *next; // 下一结点。
};
- 操作:
- 1. 创建:
// 该函数创建一个ngx_chain_t的对象,并返回指向对象的指针,失败返回NULL。
ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool);
- 2. 释放
#define ngx_free_chain(pool, cl) \
(cl)->next = (pool)->chain; \
(pool)->chain = (cl)
ngx_buf_t
- 原型:
struct ngx_buf_s {
u_char *pos; // 当buf所指向的数据在内存里的时候,pos指向的是这段数据开始的位置。
u_char *last; // 当buf所指向的数据在内存里的时候,last指向的是这段数据结束的位置。
off_t file_pos; // 当buf所指向的数据是在文件里的时候,file_pos指向的是这段数据的开始位置(在文件中的偏移量)。
off_t file_last; // 当buf所指向的数据是在文件里的时候,file_pos指向的是这段数据的结束位置(在文件中的偏移量)。
u_char *start; /* start of buffer */
u_char *end; /* end of buffer */
/* 当buf所指向的数据在内存中的时候,这以整块数据可能被拆分在不同的buf中,
start和end指向的是这一整块数据的开始和结尾
(也就是说start和end之间可能存在不属于这整块数据的内容),
而pos和last指向的是当前buf的开始和结尾。 */
ngx_buf_tag_t tag; // 实际上是一个void*类型的指针,使用者可以关联任意的对象上去,只要对使用者有意义。
ngx_file_t *file; // 当buf所包含的内容在文件中时,file字段指向对应的文件对象。
ngx_buf_t *shadow;/* 当这个buf完整copy了另外一个buf的所有字段的时候,那么这两个buf指向的实际上是同一块内存,
或者是同一个文件的同一部分,此时这两个buf的shadow字段都是指向对方的。
那么对于这样的两个buf,在释放的时候,就需要使用者特别小心,具体是由哪里释放,要提前考虑好,
如果造成资源的多次释放,可能会造成程序崩溃! */
/* the buf's content could be changed */
unsigned temporary:1; /* 为1时表示该buf所包含的内容是在一个用户创建的内存块中,
并且可以被在filter处理的过程中进行变更,而不会造成问题。*/
/*
* the buf's content is in a memory cache or in a read only memory
* and must not be changed
*/
unsigned memory:1; // 为1时表示该buf所包含的内容是在内存中,但是这些内容却不能被进行处理的filter进行变更。
/* the buf's content is mmap()ed and must not be changed */
unsigned mmap:1; // 为1时表示该buf所包含的内容是在内存中, 是通过mmap使用内存映射从文件中映射到内存中的,
// 这些内容却不能被进行处理的filter进行变更。
unsigned recycled:1;/* 可以回收的。也就是这个buf是可以被释放的。这个字段通常是配合shadow字段一起使用的,对于使用
ngx_create_temp_buf 函数创建的buf,并且是另外一个buf的shadow,
那么可以使用这个字段来标示这个buf是可以被释放的。*/
unsigned in_file:1; // 为1时表示该buf所包含的内容是在文件中。
unsigned flush:1; /* 遇到有flush字段被设置为1的buf的chain时,则该chain的数据即便不是最后结束的数据
(last_buf被设置,标志所有要输出的内容都完了),也会进行输出,
不会受postpone_output配置的限制,但是会受到发送速率等其他条件的限制。*/
unsigned sync:1;
unsigned last_buf:1;// 数据被以多个chain传递给了过滤器,此字段为1表明这是最后一个buf。
unsigned last_in_chain:1;/* 在当前的chain里面,此buf是最后一个。
需要注意的是last_in_chain的buf不一定是last_buf,但是last_buf的buf一定是last_in_chain的。
这是因为数据会被以多个chain传递给某个filter模块。 */
unsigned last_shadow:1; // 在创建一个buf的shadow的时候,通常将新创建的一个buf的last_shadow置为1。
unsigned temp_file:1; // 由于受到内存使用的限制,有时候一些buf的内容需要被写到磁盘上的临时文件中去,
// 那么这时,就设置此标志
/* STUB */ int num;
};
- 操作:
- 1. 创建:
#define ngx_alloc_buf(pool) ngx_palloc(pool, sizeof(ngx_buf_t))
#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))
//对于创建temporary字段为1的buf(就是其内容可以被后续的filter模块进行修改),
//可以直接使用函数ngx_create_temp_buf进行创建。
ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size);
/* 对于创建的这个对象,它的start和end指向新分配内存开始和结束的地方。pos和last都指向这块新分配内存的开始处,
这样,后续的操作可以在这块新分配的内存上存入数据。 */
// 为了配合对ngx_buf_t的使用,nginx定义了以下的宏方便操作。
// 1. 返回这个buf里面的内容是否在内存里。
#define ngx_buf_in_memory(b) ((b)->temporary || (b)->memory || (b)->mmap)
// 2. 返回这个buf里面的内容是否仅仅在内存里,并且没有在文件里。
#define ngx_buf_in_memory_only(b) (ngx_buf_in_memory(b) && !(b)->in_file)
// 3. 返回该buf是否是一个特殊的buf,只含有特殊的标志和没有包含真正的数据。
#define ngx_buf_special(b) \
(((b)->flush || (b)->last_buf || (b)->sync) \
&& !ngx_buf_in_memory(b) && !(b)->in_file)
// 4. 返回该buf是否是一个只包含sync标志而不包含真正数据的特殊buf。
#define ngx_buf_sync_only(b) \
((b)->sync && !ngx_buf_in_memory(b) \
&& !(b)->in_file && !(b)->flush && !(b)->last_buf)
// 5. 返回该buf所含数据的大小,不管这个数据是在文件里还是在内存里。
#define ngx_buf_size(b) \
(ngx_buf_in_memory(b) ? (off_t) ((b)->last - (b)->pos): \
((b)->file_last - (b)->file_pos))
6、链表结构:ngx_list_t
- 位置:src/core 下 ngx_list.h 和 ngx_list.c
- 原型:
typedef struct {
ngx_list_part_t *last; // 指向该链表的最后一个节点。
ngx_list_part_t part; // 该链表的首个存放具体元素的节点。
size_t size; // 链表中存放的具体元素所需内存大小。
ngx_uint_t nalloc; // 每个节点所含的固定大小的数组的容量。
ngx_pool_t *pool; // 该list使用的分配内存的pool。
} ngx_list_t;
// 再看一下ngx_list_part_t
typedef struct ngx_list_part_s ngx_list_part_t;
struct ngx_list_part_s {
void *elts; // 节点中存放具体元素的内存的开始地址。
ngx_uint_t nelts; // 节点中已有元素个数。这个值是不能大于链表头节点ngx_list_t类型中的nalloc字段的。
ngx_list_part_t *next; // 指向下一个节点。
};
- 操作:
- 1. 创建:
// 1. 该函数创建一个ngx_list_t类型的对象,并对该list的第一个节点分配存放元素的内存空间。
// pool: 分配内存使用的pool。
// n: 每个节点固定长度的数组的长度。
// size: 存放的具体元素的个数。
// 成功返回指向创建的ngx_list_t对象的指针,失败返回NULL。
ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);
/* 2. 该函数是用于ngx_list_t类型的对象已经存在,但是其第一个节点存放元素的内存空间还未分配的情况下,
可以调用此函数来给这个list的首节点来分配存放元素的内存空间。
那么什么时候会出现已经有了ngx_list_t类型的对象,而其首节点存放元素的内存尚未分配的情况呢?
那就是这个ngx_list_t类型的变量并不是通过调用ngx_list_create函数创建的。
例如:如果某个结构体的一个成员变量是ngx_list_t类型的,那么当这个结构体类型的对象被创建出来的时候,
这个成员变量也被创建出来了,但是它的首节点的存放元素的内存并未被分配。
总之,如果这个ngx_list_t类型的变量,如果不是你通过调用函数ngx_list_create创建的,那么就必须调用此函数去初始化,
否则,你往这个list里追加元素就可能引发不可预知的行为,亦或程序会崩溃! */
static ngx_inline ngx_int_t ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)
- 2. 添加:
// 该函数在给定的 list 的尾部追加一个元素,并返回指向新元素存放空间的指针。如果追加失败,则返回NULL。
void *ngx_list_push(ngx_list_t *list);
7、双向链表:ngx_queue_t
- 位置:src/core 下 ngx_queue.h 和 ngx_queue.c
- 原型:
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t *prev;
ngx_queue_t *next;
};
- 特点:
- ngx_queue_t只是声明了前向和后向指针。在使用的时候,我们首先需要定义一个哨兵节点(对于后续具体存放数据的节点,我们称之为数据节点)
#define ngx_queue_init(q) \
(q)->prev = q; \
(q)->next = q
ngx_queue_t free; // 哨兵节点
ngx_queue_init(&free); // 需要进行初始化,通过宏ngx_queue_init()来实现
// 那么如何声明一个具有数据元素的链表节点呢?只要在相应的结构体中加上一个 ngx_queue_t 的成员就行了。
// 比如ngx_http_upstream_keepalive_module中的ngx_http_upstream_keepalive_cache_t:
typedef struct {
ngx_http_upstream_keepalive_srv_conf_t *conf;
ngx_queue_t queue; // 用一个哨兵节点与之对接
ngx_connection_t *connection;
socklen_t socklen;
u_char sockaddr[NGX_SOCKADDRLEN];
} ngx_http_upstream_keepalive_cache_t;
// 对于每一个这样的数据节点,可以通过ngx_queue_insert_head()来添加到链表中,第一个参数是哨兵节点,第二个参数是数据节点,
#define ngx_queue_insert_head(h, x) \
(x)->next = (h)->next; \
(x)->next->prev = x; \
(x)->prev = h; \
(h)->next = x
#define ngx_queue_insert_after ngx_queue_insert_head
#define ngx_queue_insert_tail(h, x) \
(x)->prev = (h)->prev; \
(x)->prev->next = x; \
(x)->next = h; \
(h)->prev = x
// 比如:
ngx_http_upstream_keepalive_cache_t cache;
ngx_queue_insert_head(&free, &cache.queue);
// 可以看出哨兵节点的 prev 指向链表的尾数据节点,next 指向链表的头数据节点。
// 另外ngx_queue_head()和ngx_queue_last()这两个宏分别可以得到头节点和尾节点。
// 那假如现在有一个ngx_queue_t *q 指向的是链表中的数据节点的queue成员,如何得到ngx_http_upstream_keepalive_cache_t的数据呢?
#define ngx_queue_data(q, type, link) \
(type *) ((u_char *) q - offsetof(type, link))
// nginx提供了ngx_queue_data()宏来得到ngx_http_upstream_keepalive_cache_t的指针。
ngx_http_upstream_keepalive_cache_t *cache = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
// 另外nginx也提供了ngx_queue_remove()宏来从链表中删除一个数据节点,以及ngx_queue_add()用来将一个链表添加到另一个链表。