Nginx 队列双向链表结构 ngx_quene_t

队列链表结构

队列双向循环链表实现文件:文件:src/core/ngx_queue.h/.c。在 Nginx 的队列实现中,实质就是具有头节点的双向循环链表,这里的双向链表中的节点是没有数据区的,只有两个指向节点的指针。需注意的是队列链表的内存分配不是直接从内存池分配的,即没有进行内存池管理,而是需要我们自己管理内存,所有我们可以指定它在内存池管理或者直接在堆里面进行管理,最好使用内存池进行管理。节点结构定义如下:

/* 队列结构,其实质是具有有头节点的双向循环链表 */
typedef struct ngx_queue_s  ngx_queue_t;

/* 队列中每个节点结构,只有两个指针,并没有数据区 */
struct ngx_queue_s {
    ngx_queue_t  *prev;
    ngx_queue_t  *next;
};

队列链表操作

其基本操作如下:

/* h 为链表结构体 ngx_queue_t 的指针;初始化双链表 */
ngx_queue_int(h)

/* h 为链表容器结构体 ngx_queue_t 的指针; 判断链表是否为空 */
ngx_queue_empty(h)

/* h 为链表容器结构体 ngx_queue_t 的指针,x 为插入元素结构体中 ngx_queue_t 成员的指针;将 x 插入到链表头部 */
ngx_queue_insert_head(h, x)

/* h 为链表容器结构体 ngx_queue_t 的指针,x 为插入元素结构体中 ngx_queue_t 成员的指针。将 x 插入到链表尾部 */
ngx_queue_insert_tail(h, x)

/* h 为链表容器结构体 ngx_queue_t 的指针。返回链表容器 h 中的第一个元素的 ngx_queue_t 结构体指针 */
ngx_queue_head(h)

/* h 为链表容器结构体 ngx_queue_t 的指针。返回链表容器 h 中的最后一个元素的 ngx_queue_t 结构体指针 */
ngx_queue_last(h)

/* h 为链表容器结构体 ngx_queue_t 的指针。返回链表结构体的指针 */
ngx_queue_sentinel(h)

/* x 为链表容器结构体 ngx_queue_t 的指针。从容器中移除 x 元素 */
ngx_queue_remove(x)

/* h 为链表容器结构体 ngx_queue_t 的指针。该函数用于拆分链表,
 * h 是链表容器,而 q 是链表 h 中的一个元素。
 * 将链表 h 以元素 q 为界拆分成两个链表 h 和 n
 */
ngx_queue_split(h, q, n)

/* h 为链表容器结构体 ngx_queue_t 的指针, n为另一个链表容器结构体 ngx_queue_t 的指针
 * 合并链表,将 n 链表添加到 h 链表的末尾
 */
ngx_queue_add(h, n)

/* h 为链表容器结构体 ngx_queue_t 的指针。返回链表中心元素,即第 N/2 + 1 个 */
ngx_queue_middle(h)

/* h 为链表容器结构体 ngx_queue_t 的指针,cmpfunc 是比较回调函数。使用插入排序对链表进行排序 */
ngx_queue_sort(h, cmpfunc)

/* q 为链表中某一个元素结构体的 ngx_queue_t 成员的指针。返回 q 元素的下一个元素。*/
ngx_queue_next(q)

/* q 为链表中某一个元素结构体的 ngx_queue_t 成员的指针。返回 q 元素的上一个元素。*/
ngx_queue_prev(q)

/* q 为链表中某一个元素结构体的 ngx_queue_t 成员的指针,type 是链表元素的结构体类型名称,
 * link 是上面这个结构体中 ngx_queue_t 类型的成员名字。返回 q 元素所属结构体的地址
 */
ngx_queue_data(q, type, link)

/* q 为链表中某一个元素结构体的 ngx_queue_t 成员的指针,x 为插入元素结构体中 ngx_queue_t 成员的指针 */
ngx_queue_insert_after(q, x)

下面是队列链表操作源码的实现:

初始化链表

/* 初始化队列,即节点指针都指向自己,表示为空队列  */
#define ngx_queue_init(q)                                                         (q)->prev = q;                                                                (q)->next = q

/* 判断队列是否为空 */
#define ngx_queue_empty(h)                                                        (h == (h)->prev)

获取指定的队列链表中的节点

/* 获取队列头节点 */
#define ngx_queue_head(h)                                                         (h)->next

/* 获取队列尾节点 */
#define ngx_queue_last(h)                                                         (h)->prev

#define ngx_queue_sentinel(h)                                                     (h)

/* 获取队列指定节点的下一个节点 */
#define ngx_queue_next(q)                                                         (q)->next

/* 获取队列指定节点的前一个节点 */
#define ngx_queue_prev(q)                                                         (q)->prev

插入节点

在头节点之后插入新节点:

/* 在队列头节点的下一节点插入新节点,其中h为头节点,x为新节点 */
#define ngx_queue_insert_head(h, x)                                               (x)->next = (h)->next;                                                        (x)->next->prev = x;                                                          (x)->prev = h;                                                                (h)->next = x

插入节点比较简单,只是修改指针的指向即可。下图是插入节点的过程:注意:虚线表示断开连接,实线表示原始连接,破折线表示重新连接,图中的数字与源码步骤相对应。

在尾节点之后插入节点

/* 在队列尾节点之后插入新节点,其中h为尾节点,x为新节点 */
#define ngx_queue_insert_tail(h, x)                                               (x)->prev = (h)->prev;                                                        (x)->prev->next = x;                                                          (x)->next = h;                                                                (h)->prev = x

下图是插入节点的过程:

删除节点

删除指定的节点,删除节点只是修改相邻节点指针的指向,并没有实际将该节点的内存释放,内存释放必须由我们进行处理。

#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

删除节点过程如下图所示:

拆分链表

/* 拆分队列链表,使其称为两个独立的队列链表;
 * 其中h是原始队列的头节点,q是原始队列中的一个元素节点,n是新的节点,
 * 拆分后,原始队列以q为分界,头节点h到q之前的节点作为一个队列(不包括q节点),
 * 另一个队列是以n为头节点,以节点q及其之后的节点作为新的队列链表;
 */
#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 。链表拆分过程如下图所示:

合并链表

/* 合并两个队列链表,把n队列链表连接到h队列链表的尾部 */
#define ngx_queue_add(h, n)                                                       (h)->prev->next = (n)->next;                                                  (n)->next->prev = (h)->prev;                                                  (h)->prev = (n)->prev;                                                        (h)->prev->next = h;                                                          (n)->prev = (n)->next = n;/* 这是我个人增加的语句,若加上该语句,就不会出现头节点n会指向队列链表的节点 */

其中,h、n分别为两个队列的指针,即头节点指针,该操作将n队列链接在h队列之后。具体操作如下图所示:

获取中间节点

/* 返回队列链表中心元素 */
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作为临时指针,首先指向队列链表的头节点 */
    next = ngx_queue_head(queue);

    for ( ;; ) {
        /* 若队列链表不止一个元素,则等价于middle = middle->next */
        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;
        }
    }
}

链表排序

队列链表排序采用的是稳定的简单插入排序方法,即从第一个节点开始遍历,依次将当前节点(q)插入前面已经排好序的队列(链表)中,下面程序中,前面已经排好序的队列的尾节点为prev。操作如下:

/* 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);

        /* 首先把元素节点q独立出来 */
        ngx_queue_remove(q);

        /* 找到适合q插入的位置 */
        do {
            if (cmp(prev, q) <= 0) {
                break;
            }

            prev = ngx_queue_prev(prev);

        } while (prev != ngx_queue_sentinel(queue));

        /* 插入元素节点q */
        ngx_queue_insert_after(prev, q);
    }
}

获取队列中节点数据地址

由队列基本结构和以上操作可知,nginx 的队列操作只对链表指针进行简单的修改指向操作,并不负责节点数据空间的分配。因此,用户在使用nginx队列时,要自己定义数据结构并分配空间,且在其中包含一个 ngx_queue_t 的指针或者对象,当需要获取队列节点数据时,使用ngx_queue_data宏,其定义如下:

/* 返回q在所属结构类型的地址,type是链表元素的结构类型 */
#define ngx_queue_data(q, type, link)                                             (type *) ((u_char *) q - offsetof(type, link))
/*

测试程序:

#include <stdio.h>
#include "ngx_queue.h"
#include "ngx_conf_file.h"
#include "ngx_config.h"
#include "ngx_palloc.h"
#include "nginx.h"
#include "ngx_core.h"

#define MAX     10
typedef struct Score
{
    unsigned int score;
    ngx_queue_t Que;
}ngx_queue_score;
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, ...)
{
}

ngx_int_t CMP(const ngx_queue_t *x, const ngx_queue_t *y)
{
    ngx_queue_score *xinfo = ngx_queue_data(x, ngx_queue_score, Que);
    ngx_queue_score *yinfo = ngx_queue_data(y, ngx_queue_score, Que);

    return(xinfo->score > yinfo->score);
}

void print_ngx_queue(ngx_queue_t *queue)
{
    ngx_queue_t *q = ngx_queue_head(queue);

    printf("score: ");
    for( ; q != ngx_queue_sentinel(queue); q = ngx_queue_next(q))
    {
        ngx_queue_score *ptr = ngx_queue_data(q, ngx_queue_score, Que);
        if(ptr != NULL)
            printf(" %d\t", ptr->score);
    }
    printf("\n");
}

int main()
{
    ngx_pool_t *pool;
    ngx_queue_t *queue;
    ngx_queue_score *Qscore;

    pool = ngx_create_pool(1024, NULL);

    queue = ngx_palloc(pool, sizeof(ngx_queue_t));
    ngx_queue_init(queue);

    int i;
    for(i = 1; i < MAX; i++)
    {
        Qscore = (ngx_queue_score*)ngx_palloc(pool, sizeof(ngx_queue_score));
        Qscore->score = i;
        ngx_queue_init(&Qscore->Que);

        if(i%2)
        {
            ngx_queue_insert_tail(queue, &Qscore->Que);
        }
        else
        {
            ngx_queue_insert_head(queue, &Qscore->Que);
        }
    }

    printf("Before sort: ");
    print_ngx_queue(queue);

    ngx_queue_sort(queue, CMP);

    printf("After sort: ");
    print_ngx_queue(queue);

    ngx_destroy_pool(pool);
    return 0;

}

输出结果:

$./queue_test
Before sort: score:  8	 6	 4	 2	 1	 3	 5	 7	 9
After sort: score:  1	 2	 3	 4	 5	 6	 7	 8	 9

总结

在 Nginx 的队列链表中,其维护的是指向链表节点的指针,并没有实际的数据区,所有对实际数据的操作需要我们自行操作,队列链表实质是双向循环链表,其操作是双向链表的基本操作。

时间: 2024-11-06 07:02:09

Nginx 队列双向链表结构 ngx_quene_t的相关文章

Java队列存储结构及实现

一.队列(Queue) 队列是一种特殊的线性表,它只允许在表的前段(front)进行删除操作,只允许在表的后端(rear)进行插入操作.进行插入操作的端称为队尾,进行删除操作的端称为队头. 对于一个队列来说,每个元素总是从队列的rear端进入队列,然后等待该元素之前的所有元素出队之后,当前元素才能出对,遵循先进先出(FIFO)原则. 如果队列中不包含任何元素,该队列就被称为空队列. 二.顺序队列存储结构的实现 1 package com.ietree.basic.datastructure.qu

002 nginx的进程结构和基本配置

一 .概述 在学习nginx之前,我们首先说一下nginx的进程结构的问题. nginx是一个标准的master - worker的模型. 在nginx之中,master进程的主要任务就是完成任务的转发,将请求转发给合适的worker进程,然后workwe进程完成特定的任务. 现在我们可以简单了了解一下nginx的简单的模型. 二 .配置文件 在nginx之中,有几个我们以后需要常常使用的配置文件,现在我们首先来熟悉一下这些配置文件. 在上面的图中,我们能够看到我们几个配置文件. [1]mime

Nginx 红黑树结构 ngx_rbtree_t

概述 有关红黑树的基础知识在前面文章中已经做了介绍,想要更详细的了解红黑树可以参考文章<数据结构-红黑树>,在这里只是单纯对 Nginx 中红黑树源码的解析,Nginx 红黑树源码是实现跟算法导论中的讲解是一样的. 红黑树结构 typedef ngx_uint_t ngx_rbtree_key_t; typedef ngx_int_t ngx_rbtree_key_int_t; /* 红黑树节点结构 */ typedef struct ngx_rbtree_node_s ngx_rbtree_

I学霸官方免费教程三十六:Java数据结构之双向链表结构

数据结构之双向链表 例如:现有双向链表TwoWayLinked中存储着1,2,3,4四个元素,那么集合对象中会有4个节点A.B.C.D,由上述结构可以知道,节点A中存储着元素1和节点B:节点B中存储着元素2和节点A和节点C,节点C中存储着元素3和节点B和节点D,节点D中存储着元素4和节点C.如果现在要在元素2和3中间插入一个元素5: 过程如下: 1.创建节点E,E中存储元素5 2.将E中的上一个节点赋值为节点B 3.将B中的下一个节点修改为节点E 4.将E中的下一个节点赋值为节点C 5.将C中的

nginx 配置文件的结构

1.nginx.conf的主要部分 events { } http { server { location path { ... } location path { ... } } server { ... } } 2.配置文件的结构解释 events:配置影响nginx服务器或与用户的网络连接. http:可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置. server:配置虚拟主机的相关参数,一个http中可以有多个server. location:配置请求

数据结构之队列——顺序存储结构(php代码实现——方法一)

<?php /**  * 第一种--非循环顺序队列的实现方法  * 队列的头元素在为数组的下标为0的元素  * 这种方法的优缺点:  *  优点:头元素始终在下标为 0 的第一个元素,因此不需要设置头指针  *  缺点:元素出队是会移动大量元素,时间复杂度为O(n),效率比较低  *  */ class SqQueue{     private $SqArr;//队列存储数组     private $rear;//若队列不为空,则指向队尾元素的后一个位置     public function

nginx的ngx_http_request_t结构体

struct ngx_http_request_s { uint32_t signature; /* "HTTP" */ //请求对应的客户端连接 ngx_connection_t *connection; //指向存放所有HTTP模块的上下文结构体的指针数组 void **ctx; //指向请求对应的存放main级别配置结构体的指针数组 void **main_conf; //指向请求对应的存放srv级别配置结构体的指针数组 void **srv_conf; //指向请求对应的存放l

大话数据结构——队列顺序存储结构

#include<iostream> using namespace std; #define OK 1 #define TRUE 1 #define FALSE 0 #define ERROR 0 #define MAXSIZE 10 typedef int status;//返回的状态值 typedef int elemtype;//数据的类型 //数据结构 typedef struct queue_list { elemtype data[MAXSIZE];//队列里的元素 int re

队列(优先队列 结构体)

TonyY 有幸进入银行实习, 作为一名柜台职员, 他的任务就是在正确的时间为正确的人服务.每个来银行的人, 都有一个 vip 值, TonyY 需要做到每服务完一个客户后,找出队列中 vip 值最高的人为他服务,之后被服务的人离开队列.在他为客户服务的过程中,随时都会有新的客户来排队.现在他想知道,每次叫号的时候,叫到的人会是谁.★数据输入输入第一行为一个正整数 N(0<N<=100000),表示操作数.接下来 N 行, 每行一个操作."PUT name num"表示有一