nginx学习篇:数据结构

nginx的数据结构

nginx自己封装了很多数据结构。因为nginx具有两个特点:跨平台,c语言,所以很多库中的容器和数据结构就不能使用了,针对不同平台,nginx也封装了很多系统调用,这样在上层看来能做到统一的调用接口。

一  nginx里简单的数据结构

1.1 ngx_queue_t

 ngx_queue_t 是nginx里重要的数据结构,采用双向链表的形式,具体定义在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中有两个指针成员,都指向ngx_queue_t自身。那么nginx怎么使用ngx_queue_t呢?其实nginx只是把ngx_queue_t作为链表元素的一部分,比如下所示:
typedef struct
{
 int num;
ngx_queue_t;

}TestNode;
nginx是通过TestNode中的ngx_queue_t来控制整个链表的。控制的函数包括:
#define ngx_queue_init(q)                                                     \
    (q)->prev = q;                                                            \
    (q)->next = q


#define ngx_queue_empty(h)                                                    \
    (h == (h)->prev)


#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


#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


#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


#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;


#define ngx_queue_add(h, n)                                                   \
    (h)->prev->next = (n)->next;                                              \
    (n)->next->prev = (h)->prev;                                              \
    (h)->prev = (n)->prev;                                                    \
    (h)->prev->next = h;


#define ngx_queue_data(q, type, link)                                         \
    (type *) ((u_char *) q - offsetof(type, link))


ngx_queue_t *ngx_queue_middle(ngx_queue_t *queue);
void ngx_queue_sort(ngx_queue_t *queue,
    ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *));
注意,大部分是用宏实现的,这样没有建立函数的出栈和入栈开销,可以大大提高程序的效率。下面接下几个函数的意义:
#define ngx_queue_init(q)                                                     \
    (q)->prev = q;                                                            \
    (q)->next = q
此宏定义,主要初始化一个queue,切记,初始化的第一个queue不会包含于任何数据结构中,仅仅是表头,pre和next都指向自己。
#define ngx_queue_insert_head(h, x)                                           \
    (x)->next = (h)->next;                                                    \
    (x)->next->prev = x;                                                      \
    (x)->prev = h;                                                            \
    (h)->next = x
在容器的头部插入x,跟双向链表的插入方法一模一样,只要调整各自的pre和next就可以。
其他的类似。那么怎么通过ngx_queue_t获取存取的元素呢?即获取TestNode,我们已经知道某个ngx_queue_t的地址了,要知道TestNode的地址,只要知道TestNode中ngx_queue_t的相对偏移就可以了,这就是宏定义
#define ngx_queue_data(q, type, link)                                         \
    (type *) ((u_char *) q - offsetof(type, link))
的作用了,q是类型ngx_queue_t成员的地址,type是TestNode,link是类型ngx_queue_t。此宏会返回TestNode的地址。所有宏定义(函数)的说明如下图:
控制链表的函数:




控制容器中的元素的函数:



1.2 ngx_array_t

typedef struct {
    void        *elts;
    ngx_uint_t   nelts;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *pool;
} ngx_array_t;
elts:   指向数组的首地址;
nelts: 已经使用的数组元素的个数;
size:   每个数组元素的大小;
nalloc:总的数组元素的个数;
pool:指向内存池,动态数组最终分配的地方。
记住:ngx_array_t存放的数组一定是在pool里的,不是在栈上的。
涉及的控制函数如下:
ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
void ngx_array_destroy(ngx_array_t *a);
void *ngx_array_push(ngx_array_t *a);
void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t 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)
ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);创建一个动态数组,p指内存池,n指数组元素的个数,size指数组元素的大小。
ngx_array_create会调用ngx_init_t(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
下面看下ngx_init_t怎么实现的:
static ngx_inline ngx_int_t
ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    /*
     * set "array->nelts" before "array->elts", otherwise MSVC thinks
     * that "array->nelts" may be used without having been initialized
     */

    array->nelts = 0;
    array->size = size;
    array->nalloc = n;
    array->pool = pool;

    array->elts = ngx_palloc(pool, n * size);
    if (array->elts == NULL) {
        return NGX_ERROR;
    }

    return NGX_OK;
}
代码很容易读懂,有几个问题:1 static; 此函数定义为只是本文件可见的原因是此函数只是被ngx_array_create使用,所以对外可隐藏起来;2 加了关键字ngx_inline,看下此定义(ngx_config.h  line109):
/* TODO: auto_conf: ngx_inline   inline __inline __inline__ */
#ifndef ngx_inline
#define ngx_inline      inline
#endif
可见,是内联的意思,也就是函数被内联了,这样可以大大提高代码处理的速度,窥见nginx对速度追求。
void ngx_array_destroy(ngx_array_t *a);
只是回收了pool上的内存,实际上此内存并没有free,如下:
void
ngx_array_destroy(ngx_array_t *a)
{
    ngx_pool_t  *p;

    p = a->pool;

    if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {
        p->d.last -= a->size * a->nalloc;
    }

    if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {  //ngx_array_t本身也占用一段内存。
        p->d.last = (u_char *) a;
    }
}

void *ngx_array_push(ngx_array_t *a)  往数组里增加一个元素,这里会遇到stl的vector类似的问题,就是容器达到上限,处理方法也类似。先判断新加入的元素的大小是否会超过array的大小,不超过直接在末尾加入新元素即可,超过,先开辟一段大小是原来2倍的内存,再把原来的元素复制过去。
void *
ngx_array_push(ngx_array_t *a)
{
    void        *elt, *new;
    size_t       size;
    ngx_pool_t  *p;

    if (a->nelts == a->nalloc) {//判断是否可以继续添加元素

        /* the array is full */

        size = a->size * a->nalloc;

        p = a->pool;

        if ((u_char *) a->elts + size == p->d.last//这句话的作用是保证新添加的元素在原来容器的末尾,这样才可以像数组一样通过下标访问。
            && p->d.last + a->size <= p->d.end)
        {
            /*
             * the array allocation is the last in the pool
             * and there is space for new allocation
             */

            p->d.last += a->size;
            a->nalloc++;

        } else {
            /* allocate a new array */

            new = ngx_palloc(p, 2 * size);
            if (new == NULL) {
                return NULL;
            }

            ngx_memcpy(new, a->elts, size);
            a->elts = new;
            a->nalloc *= 2;
        }
    }

    elt = (u_char *) a->elts + a->size * a->nelts;
    a->nelts++;

    return elt;
}

1.3 ngx_list_t

在nginx的ngx_list_t.h定义了:
typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};


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类似,nelts表示已使用的数组元素个数,size表示每个数组元素的大小,nalloc指数组的大小。ngx_list_t是一个链表,但是链表的元素是数组ngx_list_part.
操作ngx_list_t的函数有:
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);
static ngx_inline ngx_int_t
ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)
ngx_list_create创建一个ngx_list_t,里面只含一个ngx_list_part_t。
ngx_list_t *
ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    ngx_list_t  *list;

    list = ngx_palloc(pool, sizeof(ngx_list_t));
    if (list == NULL) {
        return NULL;
    }

    if (ngx_list_init(list, pool, n, size) != NGX_OK) {
        return NULL;
    }

    return list;
}
调用了ngx_list_init:
static ngx_inline ngx_int_t
ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    list->part.elts = ngx_palloc(pool, n * size);
    if (list->part.elts == NULL) {
        return NGX_ERROR;
    }

    list->part.nelts = 0;
    list->part.next = NULL;
    list->last = &list->part;
    list->size = size;
    list->nalloc = n;
    list->pool = pool;

    return NGX_OK;
}
ngx_list_push只是往链表里增加一个元素
void *
ngx_list_push(ngx_list_t *l)
{
    void             *elt;
    ngx_list_part_t  *last;

    last = l->last;

    if (last->nelts == l->nalloc) {

        /* the last part is full, allocate a new list part */

        last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
        if (last == NULL) {
            return NULL;
        }

        last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
        if (last->elts == NULL) {
            return NULL;
        }

        last->nelts = 0;
        last->next = NULL;

        l->last->next = last;
        l->last = last;
    }

    elt = (char *) last->elts + l->size * last->nelts;
    last->nelts++;

    return elt;
}

二 复杂的数据结构

2.1 hash的基本数据结构

nginx的哈希表用于快速查找,nginx的hash表只在初始化时建立好,以后只能进行查询。几个数据结构:
typedef struct {
    void             *value;
    u_short           len;
    u_char            name[1];
} ngx_hash_elt_t;
基本的hash数据结构,value指用户要查询的数据,即key/value中的value。
typedef struct {
    ngx_hash_elt_t  **buckets;
    ngx_uint_t        size;
} ngx_hash_t;
此表就是hash散列表,采用了一个二级指针, 为什么采用二级指针呢 ?这就是nginx的节约内存所在(个人理解)。nginx哈希方式采用的开放寻址法,具体定义可以参考相关资料。如下图,所示,nginx先通过
key = names[n].key_hash % size;
        elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);

        elt->value = names[n].value;
        elt->len = (u_short) names[n].key.len;
计算hash槽, 再在槽里利用开放寻址法构造hash元素。


支持通配符特性,是nginx的hash函数的基本特征之一,正如我们输入“baidu.com”也能找到百度的首页一样,里面采用了通配符进行查找,即在服务器端设置了*.baidu.com的通配符。两个相关结构体:
typedef struct {
    ngx_hash_t        hash;
    void             *value;
} ngx_hash_wildcard_t;

typedef struct {
    ngx_hash_t            hash;
    ngx_hash_wildcard_t  *wc_head;//前置通配符 “*.baidu.com”
    ngx_hash_wildcard_t  *wc_tail;//后置通配符 "www.baidu.*"
} ngx_hash_combined_t;

2.2 hash表的初始化

利用三个函数,基本通配符的初始化,前置通配符的初始化和后置通配符的初始化:
ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
    ngx_uint_t nelts);//基本通配符的初始化
ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
    ngx_uint_t nelts);//前置通配符的初始化和后置通配符的初始化
   
    ngx_hash_init_t是初始化结构体,其主要作用是指定hash表的大小等上下文信息
typedef struct {
    ngx_hash_t       *hash;
    ngx_hash_key_pt   key;

    ngx_uint_t        max_size;
    ngx_uint_t        bucket_size;

    char             *name;
    ngx_pool_t       *pool;
    ngx_pool_t       *temp_pool;
} ngx_hash_init_t;
ngx_hash_key_t主要存储了hash函数得到的哈希值
typedef struct {
    ngx_str_t         key;
    ngx_uint_t        key_hash;
    void             *value;
} ngx_hash_key_t;
    利用ngx_hash_key_t对ngx_hash_init进行初始化。那么ngx_hash_key_t怎么来的呢,利用
typedef struct {
    ngx_uint_t        hsize;

    ngx_pool_t       *pool;
    ngx_pool_t       *temp_pool;

    ngx_array_t       keys;
    ngx_array_t      *keys_hash;

    ngx_array_t       dns_wc_head;
    ngx_array_t      *dns_wc_head_hash;

    ngx_array_t       dns_wc_tail;
    ngx_array_t      *dns_wc_tail_hash;
} ngx_hash_keys_arrays_t;

函数ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type);对ngx_hash_keys_arrays_t进行初始化,然后通过ngx_int_t ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, 
    void *value, ngx_uint_t flags);
    void *value, ngx_uint_t flags);构造ngx_hash_key_t。最后就可以利用ngx_hash_key_t数组来ngx_hash_init了。完成整个hash表的构造过程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值