介绍
如下图,节点ngx_queue_s分别用2个指针连接起来,prev指向上一个节点,next指向下一个节点,尾节点的next指向头,头节点的prev指向尾节点。
使用
使用比较简单,但是需要一定的步骤:
步骤1、定义容器管理链表
ngx_queue_t queue_container;
ngx_queue_init(&queue_container);
步骤2、自定义节点包含数据,并定义ngx_queue_t 成员,与1中定义的容器关联起来
自己定义节点需要包含一个ngx_queue_t的成员,位置随意,下面是一个示例。
struct {
u_char* name;
ngx_queue_t qEle;
int age;
} StudentNode;
步骤3、增删改查
链表定义和实现在ngx_queue.h与ngx_queue.c中,我们可以从中找到一些方法来使用,先定义一个包含5个节点的数组用以测试
StudentNode testNode[5] = {
{"n1",{NULL,NULL},1},
{"n2",{NULL,NULL},2},
{"n3",{NULL,NULL},3},
{"n4",{NULL,NULL},4},
{"n5",{NULL,NULL},5}
};
增
在ngx_queue.h中用宏定义实现了三种添加节点的方法,分别是添加头和尾,第一个参数代表链表容器(步骤1中定义),第二个参数代表插入的ngx_queue_t 节点(步骤二中定义的StudentNode的ngx_queue_t 成员); 三种方法都尝试插入,读者可以自己思考添加完后的数据顺序
ngx_queue_insert_head(&queue_container,&testNode[0].qEle);
ngx_queue_insert_after(&queue_container,&testNode[1].qEle);
ngx_queue_insert_tail(&queue_container,&testNode[2].qEle);
ngx_queue_insert_head(&queue_container,&testNode[3].qEle);
ngx_queue_insert_after(&queue_container,&testNode[4].qEle);
这是添加完后的打印:
删除
改
针对的是用户自定义数据,比如StudentNode 的成员name和age,与nginx链表无关
查
插入数据后整个容器的数据结构如下,双向链表的结构不变,多了一个StudentNode的成员指向链表的节点 链表可以通过前后的指针来遍历每一个节点,那么如何能通过查找链表的节点从而找到StudentNode呢? 在nginx_queue.h中,用宏定义了一个这样的函数,可以根据链表节点来获取用户定义的节点
#define offsetof(s,m) ((size_t)&(((s*)0)->m))
#define ngx_queue_data(q, type, link) (type *) ((u_char *) q - offsetof(type, link))
一堆变量可能看的有点晕,我来从结果反推,先提供一个使用案例,如下,遍历获取StudentNode
void printQueue(ngx_queue_t* ngx_queue){
for(ngx_queue_t *n = ngx_queue_head(ngx_queue);
n != ngx_queue_sentinel(ngx_queue);
n =ngx_queue_next(n))
{
StudentNode* bNode = ngx_queue_data(n, StudentNode, qEle);
printf("age %d name:%s\n",bNode->age,bNode->name);
}
}
带入自己定义的变量直接将ngx_queue_data(n, StudentNode, qEle)展开宏
(StudentNode)*((u_char *)n-offsetof(StudentNode,qEle))
这样就会看到清楚点,其中 offsetof(s,m)为
((size_t)&(((StudentNode*)0)->qEle)
相当于结构体 StudentNode 中 qEle 地址的偏移量,也就是ngx_queue_t 在用户自定义节点中的地址偏移,用图可以这样表示, 反过来当我们知道qEle的地址就能知道StudentNode节点的地址,再来看展开的宏,这里的n指向的就是qEle的地址
(StudentNode)*((u_char *)n-offsetof(StudentNode,qEle))
总结
nginx提供了一个与数据无关的双向链表,同时提供了一个方法去访问用户定义的数据(当然,用户定义的数据必须包含ngx_queue_t 成员)
参考了《深入理解nginx模块开发与架构解析》
源码验证
说明
编译源码可以单独拎出来,也可以直接加入nginx源码目录,我是加入到源码目录 如要使用本来提供的源码,编译前请先编译nginx所有源码
编译指令
gcc -o ngx_queue_main ngx_queue_main.c -I ../src/core/ -I ../objs/ -I ../src/os/unix/ ../objs/src/core/ngx_queue.o
测试源码
#include <ngx_queue.h>
#include <stdio.h>
typedef struct {
u_char* name;
ngx_queue_t qEle;
int age;
} StudentNode;
void printQueue(ngx_queue_t* ngx_queue){
for(ngx_queue_t *n = ngx_queue_head(ngx_queue);
n != ngx_queue_sentinel(ngx_queue);
n =ngx_queue_next(n))
{
StudentNode* bNode = ngx_queue_data(n, StudentNode, qEle);
printf("age %d name:%s\n",bNode->age,bNode->name);
}
}
int main(){
ngx_queue_t queue_container;
ngx_queue_init(&queue_container);
printf("pointer char size %d \n",sizeof(char*));
StudentNode testNodea = {"dmj",{NULL,NULL},1};
printf("pointer %d\n",testNodea.age);
StudentNode testNode[5] = {
{"n1",{NULL,NULL},1},
{"n2",{NULL,NULL},2},
{"n3",{NULL,NULL},3},
{"n4",{NULL,NULL},4},
{"n5",{NULL,NULL},5}
};
ngx_queue_insert_head(&queue_container,&testNode[0].qEle);
ngx_queue_insert_after(&queue_container,&testNode[1].qEle);
ngx_queue_insert_tail(&queue_container,&testNode[2].qEle);
ngx_queue_insert_head(&queue_container,&testNode[3].qEle);
ngx_queue_insert_after(&queue_container,&testNode[4].qEle);
printf("------ add ------\n");
printQueue(&queue_container);
ngx_queue_remove(&testNode[0].qEle);
printf("------ delete n1------\n");
printQueue(&queue_container);
testNode[2].age = 11;
printf("------ modify n3------\n");
printQueue(&queue_container);
ngx_queue_t* mid = ngx_queue_middle(&queue_container);
StudentNode *mid_node = ngx_queue_data(mid, StudentNode, qEle);
printf("------ mid ------\n");
printf("------ %s,%d ------\n",mid_node->name,mid_node->age);
return -1;
}
|