nginx高级数据结构---双向链表

介绍

如下图,节点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>

//编译指令
//gcc -o ngx_queue_main ngx_queue_main.c -I ../src/core/ -I ../objs/  -I ../src/os/unix/ ../objs/src/core/ngx_queue.o
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dai1396734

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值