nginx源码分析2———基础数据结构一(字符串和数组)

本文详细介绍了Nginx中的基础数据结构ngx_string_t和ngx_array_t,包括它们的定义、实现方式以及如何在Nginx源码中进行初始化、创建、元素添加和销毁等操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

相关介绍

nginx提供了大量的基础数据结构,对一些数据结构进行了封装和实现,我们在模块开发中进程会用到,当然nginx源码里面也是随处可见,熟悉基础数据结构有助于我们更快的去了解nginx源码与内部机制。

基础数据结构

ngx_string
ngx_string结构

ngx_string的实现与封装主要在ngx_string.h和ngx_string.c中,结构如下

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

1,在结构体当中,data指向字符串数据的第一个字符,字符串的结束用长度来表示,而不是由’\0’来表示结束。所以,在写nginx代码时,处理字符串的方法跟我们平时使用有很大的不一样,但要时刻记住,字符串不以’\0’结束,所以不可以用string库的函数来操作字符串,当然也没必要,nginx自己封装好了库。
2,在系统调用中,就不可以直接用nginx的字符串了,需要处理了才能直接用linux的系统调用。
3,uri、args等等,这些字符串的data部分,都是指向在接收数据时创建buffer所指向的内存中,uri,args就没有必要copy一份出来。这样的话,减少了很多不必要的内存分配与拷贝。

相关源码解析

下面我们来看一下部分源码吧
这是一段简单的把字符转化为数字的代码

/*从该函数可以看出ngx字符串处理函数并不是直接对上述结构体直接操
作,而是对其中的u_char的字符指针和size_t长度进行操作*/
ngx_int_t
ngx_atoi(u_char *line, size_t n)
{
    ngx_int_t  value, cutoff, cutlim;

    if (n == 0) {
        return NGX_ERROR;
    }
    //取出最大值得尾部与前(len - 1)位
    cutoff = NGX_MAX_INT_T_VALUE / 10;
    cutlim = NGX_MAX_INT_T_VALUE % 10;

    for (value = 0; n--; line++) {
        if (*line < '0' || *line > '9') {
            return NGX_ERROR;
        }
        //每读取一个数字就进行比较,保证下次读出不会越界。
        if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
            return NGX_ERROR;
        }
        value = value * 10 + (*line - '0');
    }
    return value;
}
相关宏解析

其实大多数函数都不是对ngx_str_t进行操作,而是取出来对指针和长度进行操作。而且字符串的处理宏特别多,函数居少。

//新建一个字符串,长度设为传统字符串长度(不包含0字符结尾)
#define ngx_string(str)  { sizeof(str) - 1, (u_char *) str }
//空字符串
#define ngx_null_string   { 0, NULL }
//这是一个将字符串变为null的宏
#define ngx_str_null(str) (str)->len = 0; (str)->data = NULL

以上是字符串的相关介绍,字符串比较简单,遇到具体的功能我们再讨论。

ngx_array_t
ngx_array_t结构

数组结构如下,ngx_pool_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_pool_t我们将在后几章介绍)

ngx_array_t初始化

我们先从初始化来了解吧,该函数是供创建调用。

//初始化容量为n的,单个元素大小为size的数组,该函数为内联函数
static ngx_inline ngx_int_t
ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    //目前数组内元素的个数
    array->nelts = 0;
    //单个元素大小为size
    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;
}
ngx_array_t创建

数组的创建,数组在创建的时候,需要内存池,然后数组的容量,以及数组单个元素的大小。

ngx_array_t *
ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size)
{
    ngx_array_t *a;
    //现在内存池中分配数组的结构体,没有分配存储数组元素的内存
    a = ngx_palloc(p, sizeof(ngx_array_t));
    if (a == NULL) {
        return NULL;
    }

    //对结构体进行赋值,并在内存池分配存储数组元素的内存
    if (ngx_array_init(a, p, n, size) != NGX_OK) {
        return NULL;
    }
    return a;
}

可以看出数组信息的结构体和数组存储元素的内存在同一个内存池(ngx_pool_t)。

ngx_array_t压入元素

下面我们再看看在数组里面压入元素

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)
        {
        /**这里面last是内存池当前分配到的地址(即已经分配的最大内存地址),end是内存池的最后(包含已经分配和未分配)**/
            /*
             * the array allocation is the last in the pool
             * and there is space for new allocation
             */
            //直接将内存池的已分配的游标后移就可以了
            p->d.last += a->size;
            //然后给容量数量+1
            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;
            //扩容1倍
            a->nalloc *= 2;
        }
    }

    //获取新加元素的地址
    elt = (u_char *) a->elts + a->size * a->nelts;
    //已经使用数量+1
    a->nelts++;
    //返回元素地址
    return elt;
}
ngx_array_t的销毁
void
ngx_array_destroy(ngx_array_t *a)
{
    ngx_pool_t  *p;
    p = a->pool;
    //如果数组位于已经分配内存的最后,那么直接将last游标前移。
    if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {
        p->d.last -= a->size * a->nalloc;
    }
    //在释放数组元素存储内存以后,如果数组结构也位于内存池已经分配内存的最后,那么仍然将内存池last游标前移
    if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {
        p->d.last = (u_char *) a;
    }
}

可以看出,如果数组不在内存池已分配内存的最后,那么销毁内存池将不做任何处理。

心得

可以看出,nginx稍微复杂一点的数据结构,就会采用内存池来实现,带来了很大的优势。由于使用ngx_palloc分配内存,数组在扩容时,旧的内存不会被释放,会造成内存的浪费。所以数组大小最好一次性搞定。对于字符串,设计比较巧妙,比较适合处理url,字符串的设计千变万化,只有适合自己业务(http)的才是最好的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值