动态数组

ngx_array_t 表示一块连续的内存,其中存放着数组元素,概念上和原始数组很接近

// 定义在 core/ngx_array.h
typedef struct
{
void * elts; // 数组的内存位置,即数组首地址
ngx_uint_t nelts; // 数组当前的元素数量
size_t size; // 数组元素的大小
ngx_uint_t nalloc; // 数组可容纳的最多元素容量
ngx_pool_t * pool; // 数组可使用的内存池
}ngx_array_t;

elts 就是原始的数组,定义成 void*,使用时应强转成相应的类型

nelts 相当于 vector.size();

size 相当于 sizeof(T);

nalloc 相当于 vector.capacity();

pool 数组使用的内存池,相当于 vector 的 allocator

数组里的元素不断增加,当 nelts > nalloc 时将引起数组扩容,ngx_array_t 会向内存池 pool 申请一块两倍原大小的空间————这个策略和 std::vector 是一样的

但 ngx_array_t 扩容成本太高,它需要重新分配内存并且将数据拷贝,所以最好一次性分配足够的空间,避免动态扩容

操作函数

使用 ngx_array_t.elts 就可以访问数组里的元素,不过需要转换为实际元素类型

auto p = reinterpret_cast<T*>(arr.elts);
cout<<p[0]<<endl;

ngx_array_t 没有越界检查,需要自行确保数组索引的有效性

// 使用内存池 p 创建一个可容纳 n 个大小为 size 元素的数组,即分配了一块 n*size 大小的内存块
ngx_array_t * ngx_array_create(ngx_pool_t * p, ngx_uint_t n, size_t size); // 销毁动态数组,归还分配的内存
void ngx_array_destory(ngx_array_t * a); // 向数组添加元素,它们返回一个 void* 指针(可添加元素的位置),需要转换类型才能再操作
// 不直接使用 elts 操作的原因是防止越界,函数内部会检查当前数组容量自动扩容
void * ngx_array_push(ngx_array_t * a); void * ngx_array_push_n(ngx_array_t * a, ngx_uint_t n);

清空数组可以直接置 nelts 为 0, 但之前分配的内存并不会释放,还可以用来存储数据


单向链表

Nginx 的单向链表 ngx_list_t 融合了 ngx_array_t 的特点,在一个节点里存储多个元素,降低了链表的存储成本

// 定义在 core/ngx_list.h
struct ngx_list_part_s
{
void * elts; // 数组元素指针
ngx_uint_t nelts; // 数组里的元素数量
ngx_list_part_t * next; // 下个节点指针
};

ngx_list_t 定义了链表,实际上是 头结点 + 元信息:

// 定义在 core/ngx_list.h
typedef struct
{
ngx_list_part_t * last; // 尾指针
ngx_list_part_t * part; // 链表头结点
size_t size; // 链表存储元素的大小
ngx_uint_t nalloc; // 每个节点能够存储元素的数量
ngx_pool_t * pool; // 链表使用的内存池
} ngx_list_t;

链表里的每一个节点就是一个简化的 ngx_array_t 数组结构

// 使用内存池创建链表,每个节点可容纳n个大小为size的元素
ngx_list_t * ngx_list_create(ngx_pool_t * pool, ngx_uint_t n, size_t size); // 向链表里添加元素,返回的指针需要转型赋值
void * ngx_list_push(ngx_list_t * list);

eg.

part = &list.part;				// 获取头结点
data = part->elts; // 获取节点内数组地址 for(i = 0; ; i++) // 遍历链表
{
if(i >= part->nelts) // 检查数组越界
{
if(part->next == NULL) // 检查是否到链表尾
{
break;
} part = part->next; // 跳至下一个节点
data = part->data; // 下一个节点的数组地址
i = 0;
} ... data[i] ... // 在本节点访问元素
}

双端队列

在Nginx 里它被实现为双向循环链表 ngx_queue_t,是侵入式容器

// 定义在 core/ngx_queue.h
struct ngx_queue_s
{
ngx_queue_t * prev; // 前驱指针
ngx_queue_t * next; // 后继指针
};

结构体需要添加它作为成员,为数据结构增加了双向链表的指针

struct X						// 一个可放入队列的数据结构
{
int x = 0; // 携带的数据
ngx_queue_t queue; // ngx_queue_t 成员,名字任意
};

结构体内可以有不止一个 ngx_queue_t 成员,这意味着它可以同时属于多个不同的双向链表

ngx_queue_t 使用一个头结点来表示队列,这个头节点可以是单纯的 ngx_queue_t 结构

// 函数宏 ngx_queue_init() 初始化头结点,把两个指针指向自身
#define ngx_queue_init(q) \
(q)->prev = q; \
(q)->next = q // 函数宏 ngx_queue_sentinel() 返回节点自身,对于头结点就相当于哨兵
#define ngx_queue_sentinel(h) (h) // ngx_queue_empty() 检查头结点的前驱指针,判断是否为空队列
#define ngx_queue_empty(h) \
(h == (h)->prev) // 函数宏 ngx_queue_insert_head() 和 ngx_queue_insert_tail() 用来向头尾插入数据节点
#define ngx_queue_insert_head(h, x)
#define ngx_queue_insert_tail(h, x) // 函数宏 ngx_queue_head() 和 ngx_queue_last() 获取队列的头尾指针
// 可以用来实现队列正向或反向遍历,直到遇到头结点 ngx_queue_sentinel()
#define ngx_queue_head(h) (h)->next
#define ngx_queue_last(h) (h)->prev // 函数 ngx_queue_sort() 使用一个比较函数指针对队列元素排序,效率不是很高
void ngx_queue_sort(ngx_queue_t * queue,
ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *));

数据节点操作

// 在节点的后面插入数据,它其实就是 ngx_queue_insert_head
#define ngx_queue_insert_after ngx_queue_insert_head // 删除节点,实际上只是调整了节点的指针,把节点从队列中摘除
#define ngx_queue_remove(x) \
(x)->next->prev = (x)->prev;\
(x)->prev->next = (x)->next; // 获取节点数据
#define ngx_queue_data(q, type, link) \
(type *)((u_char *) q - offsetof(type, link)) // 返回结构体指针(offsetof是一个宏,计算结构里成员的偏移量)

可以把双端队列分解为 节点、迭代器和队列容器三个概念:

节点保存数据,迭代器遍历数据,而队列容器则是头节点。

这三个概念可以使用C++封装成不同的类,达到解耦的目的


红黑树

在Nginx里红黑树主要用在事件机制里的定时器,检查连接超时,此外还在 reslover、cache里用于快速查找

// 定义在 core/ngx_rbtree.h
typedef ngx_uint_t ngx_rbtree_key_t;
typedef ngx_int_t ngx_rbtree_key_int_t; struct ngx_rbtree_node_s
{
ngx_rbtree_key_t key; // 红黑树的键,用于排序比较
ngx_rbtree_node_t * left; // 左节点指针
ngx_rbtree_node_t * right; // 右结点指针
ngx_rbtree_node_t * parent; // 父节点指针
u_char color; // 1 红色 / 0 黑色
u_char data; // 节点数据,只有一字节,通常无意义
};

与 ngx_queue_t 一样,ngx_rbtree_node_t 也要作为结构体的一个成员,以侵入方式来使用

例如保存字符串的红黑树节点:

typedef struct
{
ngx_rbtree_node_t node; // 红黑树节点,不必是第一个成员
ngx_str_t str; // 节点的其他信息
} ngx_str_node_t; // 节点的插入方法,函数指针类型
typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t * root, ngx_rbtree_node_t, ngx_rbtree_sentinel); struct ngx_rbtree_s
{
ngx_rbtree_node_t * root; // 红黑树的根节点
ngx_rbtree_node_t * sentinel; // 哨兵节点,相当于空指针、空对象
ngx_rbtree_insert_pt insert; // 节点的插入方法
};

insert决定了红黑树的节点插入操作,用户可以针对不同的节点类型实现不同的插入方法,但必须符合 ngx_rbtree_insert_pt 的定义

// 红黑树键值是标准的 uint/int
void ngx_rbtree_insert_value(root, node, sentinel); // 定时器红黑树专用插入函数,键值是毫秒值
void ngx_rbtree_insert_timer_value(...); // 字符串红黑树专用插入函数,键值是字符串的hash值
void ngx_str_rbtree_insert_value(...);

参考这三个函数可以实现自己的插入函数:

void ngx_rbtree_insert_value(...)		// 插入标准的红黑树,键值是整数
{
ngx_rbtree_node_t ** p; // 树节点指针
for(;;)
{ // 比较当前节点与插入节点,选择走左/右
p = (node->key < temp->key) ? &temp->left : &temp->right; if(*p == sentinel) // 直到遇到哨兵节点结束
break; temp = *p; // 移动当前指针
} *p = node; // 找到位置,插入
node->parent = temp;
node->left = sentinel;
node->right = sentinel;
ngx_rbt_red(node);
}
// 初始化宏,初始化后红黑树中仅有一个哨兵节点 s ,同时也是根节点
#define ngx_rbtree_init(tree, s, i) // tree 使用 s 作为哨兵节点,插入方法是 i // 红黑树的插入
void ngx_rbtree_insert(ngx_rbtree * tree, ngx_rbtree_node_t * node);
// 红黑树的删除
void ngx_rbtree_delete(ngx_rbtree * tree, ngx_rbtree_node_t * node);
  • 操作后若树的平衡性被破坏会自动旋转以保持平衡
// 查找最小节点,顺着指针找最左边的节点
ngx_rbtree_node_t * ngx_rbtree_min()

ngx_rbtree_min() 只会返回 ngx_rbtree_node_t* 类型,若想得到完整的结构体指针,则需要利用宏 offsetof 计算偏移量再强制类型转换

// Nginx还提供查找下一个节点的功能,利用它可以实现正序遍历红黑树
ngx_rbtree_node_t * ngx_rbtree_next(ngx_rbtree_t * tree, ngx_rbtree_node_t * node); // 对于常用的字符串红黑树,Nginx提供了专用的查找函数,它可以在树里找到任意字符串,不存在则返回 nullptr
ngx_str_node_t * ngx_str_rbtree_lookup(*rbtree, *name, hash);

缓冲区

作为web服务器,Nginx需要频繁收发处理大量的数据,这些数据有时是连续的内存块,有时是分散的内存块,甚至有时数据过大,内存无法存放,只能保存成磁盘文件

ngx_str_t 结构可以表示内存块,但不能应对复杂的情景,所以Nginx实现了 ngx_buf_t 和 ngx_chain_t

// ngx_buf_t 表示一个单块的缓冲区,既可以是内存也可以是文件
// 它的结构分为两个部分:缓冲区信息和标志位信息
typedef void * ngx_buf_tag_t; // 定义在 core/ngx_buf.h
struct ngx_buf_s
{
u_char * pos; // 内存数据的起始位置
u_char * last; // 内存数据的结束位置
off_t file_pos; // 文件数据的起始偏移量
off_t file_last; // 文件数据的结束偏移量 u_char * start; // 内存数据的上界
u_char * end; // 内存数据的下界
ngx_buf_tag_t tag; // void* 指针,可以是任意关联对象
ngx_file_t * file; // 存储数据的文件对象 ... // 标志位信息
};

因为Nginx的缓冲数据可能在内存或者磁盘文件中,所以 ngx_buf_t 使用 pos/last 和 file_pos/file_last 来指定数据在内存或文件中的具体位置,究竟数据在哪里则要靠后面的标志位信息来确定

start 和 end 两个成员变量标记了数据所在内存块的边界,如果内存块是可修改的,那么在操作时必须防止越界

tag 通常指向的是使用该缓冲区的对象

// ngx_buf_t 的标志位都是bool值,使用位域的方式节约内存

struct ngx_buf_s
{ ... // 缓冲区信息 unsigned temporary:1; // 内存块临时数据,可以修改
unsigned memory:1; // 内存块数据,不允许修改
unsigned mmap:1; // 内存映射数据,不允许修改 unsigned in_file:1; // 缓冲区在文件里
unsigned flush:1; // 要求Nginx立即输出本缓冲区
unsigned sync:1; // 要求Nginx同步操作本缓冲区
unsigned last_buf:1; // 最后一块缓冲区
unsigned last_in_chain:1;// 链里最后一块缓冲区
unsigned temp_file:1; // 缓冲区在临时文件里
};

其中 last_buf 表示整个处理过程的最后一块缓冲区,标志着 TCP/HTTP 请求处理的结束;

而 last_in_chain 表示当前数据块链(ngx_chain_t)里的最后一块,之后可能还有数据需要处理。

从 ngx_buf_t 的定义可以看到,一个有数据的缓冲区不是在内存里就是在文件里,所以内存标志位成员变量(temporary/memory/mmap)和文件标志成员变量(in_file/temp_file)不能全为0,否则Nginx 会认为这是个特殊(special)或无效的缓冲区。

如果缓冲区既不在内存也不在文件里,那么它就不含有有效数据,只起到控制作用,例如刷新(flush)或者同步(sync)

// 从内存池里分配一块 size 大小的缓冲区
ngx_buf_t * ngx_create_temp_buf(ngx_pool_t * pool, size_t size);

函数返回的 ngx_buf_t 结构内成员都已经初始化好了, pos 和 last 都指向内存块的首位置,表示空缓冲区,而temporary 标志位是1。

// 从内存池创建一个 ngx_buf_t 结构,然后手工指定它的成员,关联到已经存在的内存
#define ngx_alloc_buf(pool) ngx_palloc(pool, sizeof(ngx_buf_t))
#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t) // 检查缓冲区是否在内存里
#define ngx_buf_in_memory(b) (b->temporary || b->memory || b->mmap)
#define ngx_buf_in_memory_only(b) (ngx_buf_in_memory(b) && !b->in_file) // 判断起控制作用的特殊缓冲区
#define ngx_buf_special(b) // 计算缓冲区大小,根据是否在内存里使用恰当的指针
#define ngx_buf_size(b) // 拷贝内存,返回拷贝数据后的终点位置,在连续复制多段数据时很方便
// 定义在 core/ngx_string.h
#define ngx_cpymem(dst, src, n) (((u_char*)memcpy(dst, src, n)) + (n)) // 设置内存
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)
#define ngx_memset(buf, c, n) (void) memset(buf, c, n)

数据块链

在处理HTTP/TCP请求时会经常创建多个缓冲区来存放数据,Nginx 把缓冲区块简单地组织为一个单向链表

ngx_chain_t 把多个分散的 ngx_buf_t 连接为一个顺序的数据块链:

// 定义在 core/ngx_buf.h
struct ngx_chain_s
{
ngx_buf_t * buf; // 缓冲区指针
ngx_chain_t * next; // 下一个链表节点
}; // 从内存池里获取 ngx_chain_t 对象
ngx_chain_t * ngx_alloc_chain_link(ngx_pool_t * pool); // 内部调用 ngx_palloc(),获得的对象buf/next可能是任意值 // 释放 ngx_chain_t 对象
#define ngx_free_chain(pool, cl)

由于 ngx_chain_t 在 Nginx 里应用的很频繁,所以 Nginx 对此进行了优化。

在内存池里保存了一个空闲 ngx_chain_t 链表,分配时从这个链表中摘取,释放时再挂上去

typedef struct
{
ngx_int_t num; // 缓冲区的数量
size_t size; // 缓冲区的大小
} ngx_bufs_t; // 创建链表的参数结构 // 创建多个缓冲区,返回一个链接好的数据块链表
ngx_chain_t * ngx_create_chain_of_bufs(ngx_pool_t * pool, ngx_bufs_t * bufs);

仍然把 ngx_chain_t 分解为节点、迭代器和容器三个概念,不同C++类封装不同的操作


键值对

键值对是一种映射关系,C++使用std::pair 来表示,并且使用 std::map / std::unordered_map 存储这样的数据;

而 Nginx 提供两个结构:ngx_keyval_t 和 ngx_table_elt_t ,再结合 ngx_array_t 或 ngx_list_t 应用在不同的场合

ngx_keyval_t 是一个简单的键值对结构,主要用在 Nginx 的配置解析环节,保存配置文件里成对的配置。

// 定义在 core/ngx_string.h
typedef struct
{
ngx_str_t key;
ngx_str_t value;
} ngx_keyval_t;

在 Nginx 里,通常使用 ngx_array_t 来存储 ngx_keyval_t,相当于

typedef NgxArray<ngx_keyval_t>		NgxKvArray;

散列表键值对

// 定义在 core/ngx_hash.h
typedef struct
{
ngx_uint_t hash; // 散列(哈希)标记
ngx_str_t key; // 键
ngx_str_t value; // 值
u_char * lowcase_key; // key 的小写字符串指针
} ngx_table_elt_t;

ngx_table_elt_t 主要用来表示HTTP头部信息,例如

"Server:nginx" 这样的字符串对应到 ngx_table_elt_t 就是 key = "Server",value = "nginx"

成员 hash 是一个散列标记,Nginx使用它在散列表中快速查找数据,

可以简单地把它置为非零值(通常为1),也可以使用下面两个函数计算散列值:

ngx_uint_t ngx_hash_key(u_char * data, size_t len);		// 计算散列值

ngx_uint_t ngx_hash_key_lc(u_char * data, size_t len);	// 小写后再计算

成员 lowcase_key 指向一个全小写的字符串,在大小写无关比较时可避免重复计算。

ngx_uint_t ngx_hash_strlow(u_char * dst, u_char * src, size_t n);	// 小写化同时计算散列值

Nginx 在处理HTTP请求时使用 ngx_list_t 存储了HTTP 头部信息,相当于:

typedef NgxList<ngx_table_elt_t>	NgxHeaderList;

 

C++封装实现:https://github.com/chen892704/Nginx-Learning

Nginx学习笔记(五):高级数据结构的更多相关文章

  1. C#可扩展编程之MEF学习笔记(五):MEF高级进阶

    好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用 ...

  2. (转)Qt Model/View 学习笔记 (五)——View 类

    Qt Model/View 学习笔记 (五) View 类 概念 在model/view架构中,view从model中获得数据项然后显示给用户.数据显示的方式不必与model提供的表示方式相同,可以与 ...

  3. matlab学习笔记9 高级绘图命令_2 图形的高级控制_视点控制和图形旋转_色图和颜色映像_光照和着色

    一起来学matlab-matlab学习笔记9 高级绘图命令_2 图形的高级控制_视点控制和图形旋转_色图和颜色映像_光照和着色 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考书籍 < ...

  4. Hadoop学习笔记(7) ——高级编程

    Hadoop学习笔记(7) ——高级编程 从前面的学习中,我们了解到了MapReduce整个过程需要经过以下几个步骤: 1.输入(input):将输入数据分成一个个split,并将split进一步拆成 ...

  5. java之jvm学习笔记五(实践写自己的类装载器)

    java之jvm学习笔记五(实践写自己的类装载器) 课程源码:http://download.csdn.net/detail/yfqnihao/4866501 前面第三和第四节我们一直在强调一句话,类 ...

  6. Nginx学习笔记4 源码分析

    Nginx学习笔记(四) 源码分析 源码分析 在茫茫的源码中,看到了几个好像挺熟悉的名字(socket/UDP/shmem).那就来看看这个文件吧!从简单的开始~~~ src/os/unix/Ngx_ ...

  7. Learning ROS for Robotics Programming Second Edition学习笔记(五) indigo computer vision

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  8. Nginx学习笔记~目录索引

    回到占占推荐博客索引 前几天整理了<Docker的学习笔记索引>,受到了很多朋友的关注,今天把Nginx的文章也整理一下,以后将永久更新,像大叔之前的<EF文章系列>,< ...

  9. Typescript 学习笔记五:类

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  10. ES6学习笔记<五> Module的操作——import、export、as

    import export 这两个家伙对应的就是es6自己的 module功能. 我们之前写的Javascript一直都没有模块化的体系,无法将一个庞大的js工程拆分成一个个功能相对独立但相互依赖的小 ...

随机推荐

  1. python 画园和椭圆

    from matplotlib.patches import Ellipse, Circle import matplotlib.pyplot as plt fig = plt.figure() ax ...

  2. * resolve_conffiles: Existing conffile /etc/config/dhcp is different from the conffile in the new package. The new conffile will be placed at /etc/config/dhcp-opkg.

    * resolve_conffiles: Existing conffile /etc/config/dhcp is different from the conffile in the new pa ...

  3. linux postgresql

  4. XAMPP+TestLink

    XAMPP(Apache+MySQL+PHP+PERL)是一个功能强大的建站集成软件包.这个软件包原来的名字是 LAMPP,但是为了避免误解,最新的几个版本就改名为 XAMPP 了.它可以在Windo ...

  5. Oracle 查询表注释以及字段注释

    Oracle 查询表注释以及字段注释 --表字段信息 select * from all_tab_columns a where a.TABLE_NAME='T_X27_USER'; --表注释信息 ...

  6. PHPStorm提示:phpdoc comment doesn't contain all necessary @throw tag(s)

    选择Settings => Editor => Inspection, 选择PHP => PHPDoc => Missing @throws tag(s) ,把后面的勾勾去掉就 ...

  7. 008-MySQL报错-Access denied for user 'root'@'localhost' (using password: NO)

    1.新安装的mysql报错 MySQL报错-Access denied for user 'root'@'localhost' (using password: NO) 解决方案 1.先停掉原来的服务 ...

  8. SeetaFaceDetection识别人脸

    SeetaFaceDetection识别人脸 #pragma warning(disable: 4819) #include <seeta/FaceEngine.h> #include & ...

  9. Spring cloud微服务安全实战-4-10Zuul网关安全开发(三)

    首先把地址给它 发送post请求,请求的数据就是这个entity对象. 最后返回的值要封装到TokenInfo里面 如果一切正常的话就会拿到一个响应的实体,实体里面就包含了TokenInfo 打印实体 ...

  10. 头文件里声明和定义,Qt编译不过问题

    1.现象1 Qt5.2.1,新建头文件,声明一个类,然后在此头文件中实现类的static变量和方法,但是编译不过,显示:multiple definition of `xxx'. 2.现象2 在高版本 ...