Nginx学习(5)—— 基本结构(源码)

2023-05-16

文章目录

  • Nginx源码学习
    • 基本数据结构
      • 1、字符串结构:ngx_str_t
      • 2、类似资源管理的结构:ngx_pool_t
      • 3、Nginx数组结构:ngx_array_t
      • 4、哈希表结构:
        • (1) ngx_hash_t:普通哈希表
        • (2) ngx_hash_wildcard_t:通配符域名哈希表
        • (3) ngx_hash_combined_t:组合类型哈希表
        • (4) ngx_hash_keys_arrays_t:该结构用来预处理key
      • 5、响应消息结构:ngx_chain_t
        • ngx_buf_t
      • 6、链表结构:ngx_list_t
      • 7、双向链表:ngx_queue_t

Nginx源码学习

  • Nginx根据自己的特点实现了很多较为高效的数据结构和公共函数,所以在我们在开发的时候应该尽量调用Nginx提供的API

基本数据结构

1、字符串结构:ngx_str_t

  • 位置:src/core 下 ngx_string.h 和 ngx_string.c

  • 原型:可以看到,Nginx自定义了一个带有长度的字符串结构。这意味着,data所指向的字符串并不是以 “\0” 结束的。

    typedef struct {
    	size_t      len;
    	u_char     *data;
    } ngx_str_t;
    
  • Nginx字符串操作宏

    • 1. 构造常量字符串
      #define ngx_string(str) { sizeof(str) - 1, (u_char *) str } 
      
    • 2. 初始化字符串为空字符串
      #define ngx_null_string     { 0, NULL }
      
    • 3. 将ngx_str_t设置为text
      #define ngx_str_set(str, text) \
       (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
      
    • 4. 将ngx_str_t设置为null
      #define ngx_str_null(str)   (str)->len = 0; (str)->data = NULL
      
  • Nginx字符串操作宏的使用

    ngx_str_t str = ngx_string("hello world");
    ngx_str_t str1 = ngx_null_string;
    
    ngx_str_t str2, str3;
    ngx_str_set(&str2, "hello world"); 	
    ngx_str_null(&str3); 		
    
  • Nginx字符串操作函数

    • 1. 字符串转换:将src的前n个字符转换成小写存放到dst字符串中。str不会改变。

      void ngx_strlow(u_char *dst, u_char *src, size_t n);
      // 若想改变源字符串
      ngx_strlow(str->data, str->data, str->len);
      
    • 2. 字符串比较

      ngx_strncmp(s1, s2, n)// 区分大小写的比较前n个字符。
      ngx_strcmp(s1, s2)// 区分大小写比较两个字符串。
      
      ngx_int_t ngx_strcasecmp(u_char *s1, u_char *s2);				// 不区分大小写的字符串比较。返回0表示相等。
      ngx_int_t ngx_strncasecmp(u_char *s1, u_char *s2, size_t n);	// 不区分大小写的前n个字符的比较。
      
    • 3. 字符串格式化

      u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);
      u_char * ngx_cdecl ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...);		// 推荐使用
      u_char * ngx_cdecl ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...);	// 推荐使用
      
      // 这里特别要提醒的是,我们最常用于格式化ngx_str_t结构,其对应的转义符是%V,传给函数的一定要是指针类型。
      ngx_str_t str = ngx_string("hello world");
      char buffer[1024];
      ngx_snprintf(buffer, 1024, "%V", &str); // 注意,str取地址
      
      • 关于Nginx中格式化参数在ngx_string.c中有说明
        在这里插入图片描述
    • 4. 字符串BASE64编解码:将结果放到dst中,需要保证dst有足够的空间存放结果。

      // 如果不知道具体大小,可先调用ngx_base64_encoded_length与ngx_base64_decoded_length来预估最大占用空间。
      #define ngx_base64_encoded_length(len)  (((len + 2) / 3) * 4)
      #define ngx_base64_decoded_length(len)  (((len + 3) / 4) * 3)
      
      void ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src);
      ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);
      
    • 5. 字符串ESCAPE编码:对src按照type方式编码,结果放到dst中。如果dst传NULL可获得结果的空间大小。

      // type可以是:
      #define NGX_ESCAPE_URI 0
      #define NGX_ESCAPE_ARGS 1
      #define NGX_ESCAPE_HTML 2
      #define NGX_ESCAPE_REFRESH 3
      #define NGX_ESCAPE_MEMCACHED 4
      #define NGX_ESCAPE_MAIL_AUTH 5
      
      uintptr_t ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type);
      
      // 反escape编码:
      // type可以是 0 或下面的宏:
      #define NGX_UNESCAPE_URI       1
      #define NGX_UNESCAPE_REDIRECT  2
      
      void ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type);
      
      • 如果type是0,则表示src中的所有字符都要进行转码。如果是NGX_UNESCAPE_URI与NGX_UNESCAPE_REDIRECT,则遇到’?’后就结束了,后面的字符就不管了。而NGX_UNESCAPE_URI与NGX_UNESCAPE_REDIRECT之间的区别是NGX_UNESCAPE_URI对于遇到的需要转码的字符,都会转码,而NGX_UNESCAPE_REDIRECT则只会对非可见字符进行转码。
    • 6. 对html标签进行编码:dst传NULL返回结果占用空间大小。

      uintptr_t ngx_escape_html(u_char *dst, u_char *src, size_t size);
      
    • 7. 对JSON进行编码:dst传NULL返回结果占用空间大小。

      uintptr_t ngx_escape_json(u_char *dst, u_char *src, size_t size);
      

2、类似资源管理的结构:ngx_pool_t

  • 位置:src/core 下 ngx_palloc.h 和 ngx_palloc.c
  • 原型
    typedef struct ngx_pool_s            ngx_pool_t;
    
    struct ngx_pool_s {
        ngx_pool_data_t       d;
        size_t                max;
        ngx_pool_t           *current;
        ngx_chain_t          *chain;
        ngx_pool_large_t     *large;
        ngx_pool_cleanup_t   *cleanup;
        ngx_log_t            *log;
    };
    
  • 操作
    • 1. 创建一个初始节点大小为size的pool:log为后续在该pool上进行操作时输出日志的对象。

      // size的有效最大值为NGX_MAX_ALLOC_FROM_POOL,若传入的值超过NGX_MAX_ALLOC_FROM_POOL,
      // 则实际用到的不会超过NGX_MAX_ALLOC_FROM_POOL,造成浪费。
      // size还必须大于sizeof(ngx_pool_t),否则会导致程序崩溃,因为在这片内存中需要存储ngx_pool_t本身。
      ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
      
    • 2. 内存分配

      // 从这个pool中分配一块为size大小的内存。此函数分配的内存的起始地址按照NGX_ALIGNMENT进行了对齐。
      // 对齐操作会提高系统处理的速度,但会造成少量内存的浪费。
      void *ngx_palloc(ngx_pool_t *pool, size_t size);
      
      // 从这个pool中分配一块为size大小的内存。但是此函数分配的内存并没有像上面的函数那样进行对齐。
      void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
      
      // 该函数也是分配size大小的内存,并且对分配的内存块进行了清零。内部实际上是调用ngx_palloc实现的。
      void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
      
      // 按照指定对齐大小alignment来申请一块大小为size的内存。
      // 此处获取的内存不管大小都将被置于大内存块链中管理。(也就是pool->large管理的链表)
      void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
      
    • 3. 内存释放

      // 对于被置于大块内存链,也就是被large字段管理的一列内存中的某块进行释放。(也就是释放pool->large所管理链表的某个节点)
      // 该函数的实现是顺序遍历large管理的大块内存链表。所以效率比较低下。
      // 如果在这个链表中找到了这块内存,则释放,并返回NGX_OK。否则返回NGX_DECLINED。
      ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
      
      
      // ngx_pool_t中的cleanup字段管理着一个特殊的链表,该链表的每一项都记录着一个特殊的需要释放的资源。
      // 对于这个链表中每个节点所包含的资源如何去释放,是自说明的。
      // 这也就提供了非常大的灵活性。意味着,ngx_pool_t不仅仅可以管理内存,通过这个机制,也可以管理任何需要释放的资源。
      // 这个 size 就是要存储这个data字段所指向的资源的大小,该函数会为data分配size大小的空间。
      ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
      // 看一下 ngx_pool_cleanup_t 结构
      typedef void (*ngx_pool_cleanup_pt)(void *data);
      typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;
      struct ngx_pool_cleanup_s {
          ngx_pool_cleanup_pt   handler;	// 是一个函数指针,指向一个可以释放data所对应资源的函数。该函数只有一个参数,就是data。
          void                 *data;		// 指明了该节点所对应的资源。
          ngx_pool_cleanup_t   *next;		// 指向该链表中下一个元素。
      };
      
      
      // 该函数就是释放pool中持有的所有内存,以及依次调用cleanup字段所管理的链表中每个元素的handler字段所指向的函数。
      // 释放掉所有该pool管理的资源。并且把pool指向的ngx_pool_t也释放掉。
      void ngx_destroy_pool(ngx_pool_t *pool);
      
      
      // 该函数释放pool中所有大块内存链表上的内存,小块内存链上的内存块都修改为可用。但是不会去处理cleanup链表上的结点。
      void ngx_reset_pool(ngx_pool_t *pool);
      

3、Nginx数组结构:ngx_array_t

  • 位置:src/core 下 ngx_array.c 和 ngx_array.h
  • 原型
    typedef struct {
        void        *elts;		// 指向实际的数据存储区域。
        ngx_uint_t   nelts;		// 数组实际元素个数。
        size_t       size;		// 数组单个元素的大小,单位是字节。
        ngx_uint_t   nalloc;	/* 数组的容量。表示该数组在不引发扩容的前提下,可以最多存储的元素的个数。
        						   当nelts增长到达nalloc 时,如果再往此数组中存储元素,则会引发数组的扩容。
        						   数组的容量将会扩展到原有容量的2倍大小。实际上是分配新的一块内存,
        						   新的一块内存的大小是原有内存大小的2倍。原有的数据会被拷贝到新的一块内存中。*/
        ngx_pool_t  *pool;		// 该数组用来分配内存的内存池。
    } ngx_array_t;
    
  • 操作
    • 1. 创建数组对象
      // p:数组分配内存使用的内存池;
      // n:数组的初始容量大小,即在不扩容的情况下最多可以容纳的元素个数。
      // size:单个元素的大小,单位是字节。
      ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
      
    • 2. 添加新元素:在数组a上新追加一个元素,并返回指向新元素的指针。
      // 需要把返回的指针使用类型转换,转换为具体的类型,然后再给新元素本身或者是各字段(如果数组的元素是复杂类型)赋值。
      void *ngx_array_push(ngx_array_t *a);
      
    • 3. 批量添加元素:在数组a上追加n个元素,并返回指向这些追加元素的首个元素的位置的指针。
      void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);
      
    • 4. 销毁数组对象
      // 销毁该数组对象,并将其内存放回内存池。
      void ngx_array_destroy(ngx_array_t *a);
      
    • 5. 初始化
      // 如果一个数组对象是被分配在栈上的,那么就需要调用此函数,进行初始化的工作以后,才可以使用。
      
      // 由于使用ngx_palloc分配内存,数组在扩容时,旧的内存不会被释放,会造成内存的浪费。(ngx_array_push和ngx_array_push_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);
      

4、哈希表结构:

(1) ngx_hash_t:普通哈希表

  • 位置:src/core 下 ngx_hash.h 和 ngx_hash.c
  • 原型
    typedef struct {
        ngx_hash_elt_t  **buckets;
        ngx_uint_t        size;
    } ngx_hash_t;
    
  • Nginx哈希表的特点
    • ngx_hash_t解决哈希冲突的方式类似开链法。
    • ngx_hash_t不像其他的hash表的实现,可以插入删除元素,它只能一次初始化,就构建起整个hash表以后,既不能再删除,也不能在插入元素了。
    • ngx_hash_t的开链并不是真的开了一个链表,实际上是开了一段连续的存储空间,几乎可以看做是一个数组。这是因为ngx_hash_t在初始化的时候,会经历一次预计算的过程,提前把每个桶里面会有多少元素放进去给计算出来,这样就提前知道每个桶的大小了。那么就不需要使用链表,一段连续的存储空间就足够了。这也从一定程度上节省了内存的使用。
  • 操作
    • 1. 初始化函数
      // hinit是初始化的一些参数的一个集合。
      // names是初始化一个 ngx_hash_t 所需要的所有 key 的一个数组。
      // nelts就是key的个数。
      ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);
      
      // 看一下 ngx_hash_init_t 结构
      typedef struct {
          ngx_hash_t       *hash;	// 该字段如果为NULL,那么调用完初始化函数后,该字段指向新创建出来的hash表。
          						// 如果该字段不为NULL,那么在初始化的时候,所有的数据被插入到这个字段所指的hash表中。
          ngx_hash_key_pt   key;	// 指向从字符串生成hash值的hash函数。nginx的源代码中提供了默认的实现函数ngx_hash_key_lc。
      
          ngx_uint_t        max_size;		// hash表中的桶的个数。
          								// 该字段越大,元素存储时冲突的可能性越小,每个桶中存储的元素会更少,则查询起来的速度更快。
          								// 当然,这个值越大,越造成内存的浪费也越大。
          ngx_uint_t        bucket_size;	// 每个桶的最大限制大小,单位是字节。
      
          char             *name;			// 该 hash 表的名字。
          ngx_pool_t       *pool;			// 该hash表分配内存使用的pool。
          ngx_pool_t       *temp_pool;	// 该hash表使用的临时pool,在初始化完成以后,该pool可以被释放和销毁掉。
      } ngx_hash_init_t;
      
      // 再看一下 ngx_hash_key_t 结构
      typedef struct {
          ngx_str_t         key;
          ngx_uint_t        key_hash;	// key的 hash 值
          void             *value;
      } ngx_hash_key_t;
      
    • 2. 查找
      // 在hash里面查找key对应的value。
      // 实际上这里的key是对真正的key(也就是name)计算出的hash值。
      // len是name的长度。
      // 如果查找成功,则返回指向value的指针,否则返回NULL。
      void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);
      

(2) ngx_hash_wildcard_t:通配符域名哈希表

  • 位置:src/core 下 ngx_hash.c 和 ngx_hash.h
  • 原型
    typedef struct {
        ngx_hash_t        hash;
        void             *value;
    } ngx_hash_wildcard_t;
    
  • 特点
    • nginx为了处理带有通配符的域名的匹配问题,实现了ngx_hash_wildcard_t这样的hash表。
    • 他可以支持两种类型的带有通配符的域名。
      • 通配符在前的,例如:“*.abc.com”,也可以省略掉星号,直接写成”.abc.com”。
      • 另外一种是通配符在末尾的,例如:“mail.xxx.*”,需要特别注意通配符在末尾的不可以被省略掉。
  • 注意
    • 一个ngx_hash_wildcard_t类型的hash表只能包含通配符在前的key或者是通配符在后的key。不能同时包含两种类型通配符的key。
  • 操作
    • 1. 初始化

      // 该函数迎来构建一个可以包含通配符key的hash表。该函数执行成功返回NGX_OK,否则NGX_ERROR。
      // hinit: 构造一个通配符hash表所需参数的一个集合。
      // nelts: names数组元素的个数。
      ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);
      
      • 对names参数的说明:
        • 构造此hash表的所有的通配符key的数组。需要注意这里的key都是已经被预处理过的。例如:“*.abc.com”或者“.abc.com”被预处理完成以后,变成了“com.abc.”。而“mail.xxx.*”则被预处理为“mail.xxx.”。
          为什么会被处理这样?这里不得不简单地描述一下通配符hash表的实现原理。
          当构造此类型的hash表的时候,实际上是构造了一个hash表的一个“链表”,是通过hash表中的key“链接”起来的。比如:对于“*.abc.com”将会构造出2个hash表,第一个hash表中有一个key为com的表项,该表项的value包含有指向第二个hash表的指针,而第二个hash表中有一个表项abc,该表项的value包含有指向*.abc.com对应的value的指针。那么查询的时候,比如查询www.abc.com的时候,先查com,通过查com可以找到第二级的hash表,在第二级hash表中,再查找abc,依次类推,直到在某一级的hash表中查到的表项对应的value对应一个真正的值而非一个指向下一级hash表的指针的时候,查询过程结束。
          这里有一点需要特别注意的是names数组中元素的value值低两位bit必须为0(有特殊用途)。如果不满足这个条件,这个hash表查询不出正确结果。
    • 2. 查询

// 该函数查询包含通配符在前的key的hash表的。
void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);

// 该函数查询包含通配符在末尾的key的hash表的。
void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);

// hwc: hash表对象的指针。
// name: 需要查询的域名,例如: www.abc.com。
// len: name的长度。

(3) ngx_hash_combined_t:组合类型哈希表

  • 位置:src/core 下 ngx_hash.h 和 ngx_hash.c
  • 原型
    // 该类型实际上包含了三个hash表,一个普通hash表,一个包含前向通配符的hash表和一个包含后向通配符的hash表。
    typedef struct {
        ngx_hash_t            hash;
        ngx_hash_wildcard_t  *wc_head;
        ngx_hash_wildcard_t  *wc_tail;
    } ngx_hash_combined_t;
    
  • 操作
    • 查询
      // 该函数在此组合hash表中,依次查询其三个子hash表,看是否匹配,一旦找到,立即返回查找结果,
      // 也就是说如果有多个可能匹配,则只返回第一个匹配的结果。
      // 成功返回查询的结果,未查到则返回NULL。
      void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name, size_t len);
      
      // hash: 此组合hash表对象。
      // key: 根据key计算出的hash值。
      // name: key实际的具体内容。
      // len: name的长度。
      

(4) ngx_hash_keys_arrays_t:该结构用来预处理key

  • 位置:src/core 下 ngx_hash.h 和 ngx_hash.c
  • 原型
可以看到在构建一个ngx_hash_wildcard_t的时候,需要对通配符的哪些key进行预处理。
这个处理起来比较麻烦。而当有一组key,这些里面既有无通配符的key,也有包含通配符的key的时候。
我们就需要构建三个hash表,一个包含普通的key的hash表,一个包含前向通配符的hash表,
一个包含后向通配符的hash表(或者也可以把这三个hash表组合成一个ngx_hash_combined_t)。
在这种情况下,为了让大家方便的构造这些hash表,nginx提供给了此辅助类型。

typedef struct {
    ngx_uint_t        hsize;	// 将要构建的hash表的桶的个数。对于使用这个结构中包含的信息构建的三种类型的hash表都会使用此参数。

    ngx_pool_t       *pool;		// 构建这些hash表使用的pool。
    ngx_pool_t       *temp_pool;// 在构建这个类型以及最终的三个hash表过程中可能用到临时pool。该temp_pool可以在构建完成以后,被销毁掉。

    ngx_array_t       keys;		// 存放所有非通配符key的数组。
    ngx_array_t      *keys_hash;// 这是个二维数组,第一个维度代表的是bucket的编号,
    							// 那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key。
    							/* 假设有3个key,分别是key1,key2和key3假设hash值算出来以后对hsize取模的值都是i,
    							那么这三个key的值就顺序存放在keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。
    							该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复。*/

    ngx_array_t       dns_wc_head;	// 放前向通配符key被处理完成以后的值。
    								// 比如:“*.abc.com” 被处理完成以后,变成 “com.abc.” 被存放在此数组中。
    ngx_array_t      *dns_wc_head_hash; // 同理 keys_hash

    ngx_array_t       dns_wc_tail;	// 存放后向通配符key被处理完成以后的值。
    								// 比如:“mail.xxx.*” 被处理完成以后,变成 “mail.xxx.” 被存放在此数组中。
    ngx_array_t      *dns_wc_tail_hash;	// 同理 keys_hash
} ngx_hash_keys_arrays_t;
  • 操作
    • 初始化
      // 初始化ngx_hash_keys_arrays_t结构,主要是对这个结构中的ngx_array_t类型的字段进行初始化,成功返回NGX_OK。
      ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type);
      
      // type: 该字段有2个值可选择,即 NGX_HASH_SMALL 和 NGX_HASH_LARGE 用来指明将要建立的hash表的类型,
      // 如果是 NGX_HASH_SMALL,则有比较小的桶的个数和数组元素大小。NGX_HASH_LARGE则相反。
      
    • 添加:有关于这个数据结构的使用,可以参考src/http/ngx_http.c中的ngx_http_server_names函数。
      // 一般是循环调用这个函数,把一组键值对加入到这个结构体中。返回NGX_OK是加入成功。返回NGX_BUSY意味着key值重复。
      ngx_int_t ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value, ngx_uint_t flags);
      
      /* flags: 有两个标志位可以设置, NGX_HASH_WILDCARD_KEY 和 NGX_HASH_READONLY_KEY 。
      同时要设置的使用按位或操作符就可以了。NGX_HASH_READONLY_KEY被设置的时候,在计算hash值的时候,key的值不会被转成小写字符,否则会。
      NGX_HASH_WILDCARD_KEY被设置的时候,说明key里面可能含有通配符,会进行相应的处理。
      如果两个标志位都不设置,传0。*/
      

5、响应消息结构:ngx_chain_t

  • 位置:src/core 下 ngx_buf.h 和 ngx_buf.c
  • 原型
typedef struct ngx_chain_s           ngx_chain_t;
struct ngx_chain_s {
    ngx_buf_t    *buf;		// 实际数据。
    ngx_chain_t  *next;		// 下一结点。
};
  • 操作
    • 1. 创建
      // 该函数创建一个ngx_chain_t的对象,并返回指向对象的指针,失败返回NULL。
      ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool);
      
    • 2. 释放
      #define ngx_free_chain(pool, cl)            \
      (cl)->next = (pool)->chain;             \
      (pool)->chain = (cl)
      

ngx_buf_t

  • 原型
struct ngx_buf_s {
    u_char          *pos;	// 当buf所指向的数据在内存里的时候,pos指向的是这段数据开始的位置。
    u_char          *last;	// 当buf所指向的数据在内存里的时候,last指向的是这段数据结束的位置。
    off_t            file_pos;	// 当buf所指向的数据是在文件里的时候,file_pos指向的是这段数据的开始位置(在文件中的偏移量)。
    off_t            file_last;	// 当buf所指向的数据是在文件里的时候,file_pos指向的是这段数据的结束位置(在文件中的偏移量)。

    u_char          *start;         /* start of buffer */
    u_char          *end;           /* end of buffer */
    							/* 当buf所指向的数据在内存中的时候,这以整块数据可能被拆分在不同的buf中,
    							start和end指向的是这一整块数据的开始和结尾
    							(也就是说start和end之间可能存在不属于这整块数据的内容),
    							而pos和last指向的是当前buf的开始和结尾。 */
    ngx_buf_tag_t    tag;	// 实际上是一个void*类型的指针,使用者可以关联任意的对象上去,只要对使用者有意义。
    ngx_file_t      *file;	// 当buf所包含的内容在文件中时,file字段指向对应的文件对象。
    ngx_buf_t       *shadow;/* 当这个buf完整copy了另外一个buf的所有字段的时候,那么这两个buf指向的实际上是同一块内存,
							    或者是同一个文件的同一部分,此时这两个buf的shadow字段都是指向对方的。
							    那么对于这样的两个buf,在释放的时候,就需要使用者特别小心,具体是由哪里释放,要提前考虑好,
							    如果造成资源的多次释放,可能会造成程序崩溃! */


    /* the buf's content could be changed */
    unsigned         temporary:1;	/* 为1时表示该buf所包含的内容是在一个用户创建的内存块中,
    								并且可以被在filter处理的过程中进行变更,而不会造成问题。*/

    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;	// 为1时表示该buf所包含的内容是在内存中,但是这些内容却不能被进行处理的filter进行变更。

    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;	// 为1时表示该buf所包含的内容是在内存中, 是通过mmap使用内存映射从文件中映射到内存中的,
    							// 这些内容却不能被进行处理的filter进行变更。

    unsigned         recycled:1;/* 可以回收的。也就是这个buf是可以被释放的。这个字段通常是配合shadow字段一起使用的,对于使用
								ngx_create_temp_buf 函数创建的buf,并且是另外一个buf的shadow,
								那么可以使用这个字段来标示这个buf是可以被释放的。*/
    unsigned         in_file:1;	// 为1时表示该buf所包含的内容是在文件中。
    unsigned         flush:1;	/* 遇到有flush字段被设置为1的buf的chain时,则该chain的数据即便不是最后结束的数据
    								(last_buf被设置,标志所有要输出的内容都完了),也会进行输出,
    								不会受postpone_output配置的限制,但是会受到发送速率等其他条件的限制。*/
    unsigned         sync:1;	
    unsigned         last_buf:1;// 数据被以多个chain传递给了过滤器,此字段为1表明这是最后一个buf。
    unsigned         last_in_chain:1;/* 在当前的chain里面,此buf是最后一个。
    						需要注意的是last_in_chain的buf不一定是last_buf,但是last_buf的buf一定是last_in_chain的。
    						这是因为数据会被以多个chain传递给某个filter模块。 */

    unsigned         last_shadow:1;	// 在创建一个buf的shadow的时候,通常将新创建的一个buf的last_shadow置为1。
    unsigned         temp_file:1;	// 由于受到内存使用的限制,有时候一些buf的内容需要被写到磁盘上的临时文件中去,
    								// 那么这时,就设置此标志 
    
    /* STUB */ int   num;
};
  • 操作
    • 1. 创建
      #define ngx_alloc_buf(pool)  ngx_palloc(pool, sizeof(ngx_buf_t))
      #define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))
      
      
      //对于创建temporary字段为1的buf(就是其内容可以被后续的filter模块进行修改),
      //可以直接使用函数ngx_create_temp_buf进行创建。
      ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size);
      /* 对于创建的这个对象,它的start和end指向新分配内存开始和结束的地方。pos和last都指向这块新分配内存的开始处,
      这样,后续的操作可以在这块新分配的内存上存入数据。 */
      
      
      // 为了配合对ngx_buf_t的使用,nginx定义了以下的宏方便操作。
      // 1. 返回这个buf里面的内容是否在内存里。
      #define ngx_buf_in_memory(b)       ((b)->temporary || (b)->memory || (b)->mmap)
      
      // 2. 返回这个buf里面的内容是否仅仅在内存里,并且没有在文件里。
      #define ngx_buf_in_memory_only(b)  (ngx_buf_in_memory(b) && !(b)->in_file)
      
      // 3. 返回该buf是否是一个特殊的buf,只含有特殊的标志和没有包含真正的数据。
      #define ngx_buf_special(b)                                                   \
      		 (((b)->flush || (b)->last_buf || (b)->sync)                              \
      			&& !ngx_buf_in_memory(b) && !(b)->in_file)
      
      // 4. 返回该buf是否是一个只包含sync标志而不包含真正数据的特殊buf。
      #define ngx_buf_sync_only(b)                                                 \
      	 ((b)->sync && !ngx_buf_in_memory(b)                                      \
      		&& !(b)->in_file && !(b)->flush && !(b)->last_buf)
      
      // 5. 返回该buf所含数据的大小,不管这个数据是在文件里还是在内存里。
      #define ngx_buf_size(b)                                                      \
      	(ngx_buf_in_memory(b) ? (off_t) ((b)->last - (b)->pos):                  \
                          		((b)->file_last - (b)->file_pos))
      

6、链表结构:ngx_list_t

  • 位置:src/core 下 ngx_list.h 和 ngx_list.c
  • 原型
typedef struct {
    ngx_list_part_t  *last;		// 指向该链表的最后一个节点。
    ngx_list_part_t   part;		// 该链表的首个存放具体元素的节点。
    size_t            size;		// 链表中存放的具体元素所需内存大小。
    ngx_uint_t        nalloc;	// 每个节点所含的固定大小的数组的容量。
    ngx_pool_t       *pool;		// 该list使用的分配内存的pool。
} ngx_list_t;

// 再看一下ngx_list_part_t  
typedef struct ngx_list_part_s  ngx_list_part_t;
struct ngx_list_part_s {
    void             *elts;		// 节点中存放具体元素的内存的开始地址。
    ngx_uint_t        nelts;	// 节点中已有元素个数。这个值是不能大于链表头节点ngx_list_t类型中的nalloc字段的。
    ngx_list_part_t  *next;		// 指向下一个节点。
};
  • 操作
    • 1. 创建
      // 1. 该函数创建一个ngx_list_t类型的对象,并对该list的第一个节点分配存放元素的内存空间。
      // pool: 分配内存使用的pool。
      // n: 每个节点固定长度的数组的长度。
      // size: 存放的具体元素的个数。
      // 成功返回指向创建的ngx_list_t对象的指针,失败返回NULL。
      ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);
      
      /* 2. 该函数是用于ngx_list_t类型的对象已经存在,但是其第一个节点存放元素的内存空间还未分配的情况下,
      可以调用此函数来给这个list的首节点来分配存放元素的内存空间。
      那么什么时候会出现已经有了ngx_list_t类型的对象,而其首节点存放元素的内存尚未分配的情况呢?
      那就是这个ngx_list_t类型的变量并不是通过调用ngx_list_create函数创建的。
      例如:如果某个结构体的一个成员变量是ngx_list_t类型的,那么当这个结构体类型的对象被创建出来的时候,
      这个成员变量也被创建出来了,但是它的首节点的存放元素的内存并未被分配。
      总之,如果这个ngx_list_t类型的变量,如果不是你通过调用函数ngx_list_create创建的,那么就必须调用此函数去初始化,
      否则,你往这个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)
      
    • 2. 添加
      // 该函数在给定的 list 的尾部追加一个元素,并返回指向新元素存放空间的指针。如果追加失败,则返回NULL。
      void *ngx_list_push(ngx_list_t *list);
      

7、双向链表:ngx_queue_t

  • 位置:src/core 下 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只是声明了前向和后向指针。在使用的时候,我们首先需要定义一个哨兵节点(对于后续具体存放数据的节点,我们称之为数据节点)
#define ngx_queue_init(q)                                                     \
    (q)->prev = q;                                                            \
    (q)->next = q

ngx_queue_t free;	// 哨兵节点
ngx_queue_init(&free);	// 需要进行初始化,通过宏ngx_queue_init()来实现


// 那么如何声明一个具有数据元素的链表节点呢?只要在相应的结构体中加上一个 ngx_queue_t 的成员就行了。
// 比如ngx_http_upstream_keepalive_module中的ngx_http_upstream_keepalive_cache_t:
typedef struct {
	ngx_http_upstream_keepalive_srv_conf_t *conf;
	ngx_queue_t queue;			// 用一个哨兵节点与之对接
	ngx_connection_t *connection;
	socklen_t socklen;
	u_char sockaddr[NGX_SOCKADDRLEN];
} ngx_http_upstream_keepalive_cache_t;
// 对于每一个这样的数据节点,可以通过ngx_queue_insert_head()来添加到链表中,第一个参数是哨兵节点,第二个参数是数据节点,
#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
// 比如:
ngx_http_upstream_keepalive_cache_t cache;
ngx_queue_insert_head(&free, &cache.queue);
// 可以看出哨兵节点的 prev 指向链表的尾数据节点,next 指向链表的头数据节点。
// 另外ngx_queue_head()和ngx_queue_last()这两个宏分别可以得到头节点和尾节点。

// 那假如现在有一个ngx_queue_t *q 指向的是链表中的数据节点的queue成员,如何得到ngx_http_upstream_keepalive_cache_t的数据呢?
#define ngx_queue_data(q, type, link)                                         \
    (type *) ((u_char *) q - offsetof(type, link))
// nginx提供了ngx_queue_data()宏来得到ngx_http_upstream_keepalive_cache_t的指针。
ngx_http_upstream_keepalive_cache_t *cache = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);

// 另外nginx也提供了ngx_queue_remove()宏来从链表中删除一个数据节点,以及ngx_queue_add()用来将一个链表添加到另一个链表。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Nginx学习(5)—— 基本结构(源码) 的相关文章

随机推荐

  • 解决 LiveQing 流媒体服务器videojs flash播放RTMP、HLS提示错误的问题

    问题 LiveQing流媒体服务器可以输出HTTP FLV Websocket FLV RTMP HLS流 xff0c 在做RTMP HLS flash播放时候 xff0c 经常会遇到网站flash被禁用的情况 xff0c 每一次都会有用户
  • webpack 混淆压缩 javascript 后端代码

    需求背景 JavaScript 是脚本语言 没有编译过程 直接以源码就可以运行 有的时候 出于安全或者其他的原因 我们不希望别人直接读到源码 或者很容易对源码做出修改使用 这个时候 就需要对源码进行混淆压缩处理 经过处理后的代码体积变小 不
  • VideoJS 网页直播实现默认静音

    在开发 LiveQing高性能流媒体服务器 网页直播多分屏的时候 产品提出议建说 能不能在多分屏的时候 默认静音状态 因为多分屏界面 如果声音打开 好多个直播画面同时发出声音太过嘈杂 体验不好 我表示赞同 心想 这个应该不难处理吧 结果踩到
  • VideoJS 网页直播实现双击全屏

    最近接到客户需求 要求我们的网页直播播放器更加符合广大人民群众的使用习惯 实现双击全屏的效果 目前网页直播播放器使用了开源的 VideoJS 它的默认效果是单击播放区域暂停 只能通过右下角的最大化按钮触发最大化 要实现双击全屏播放的效果 就
  • LivePlayer H5直播/点播播放器安装与使用

    LivePlayer H5播放器 简介 H5直播 点播播放器 xff0c 使用简单 xff0c 功能强大 xff0c 免费使用 支持m3u8播放 支持HTTP FLV播放 支持RTMP播放 支持直播和点播播放 支持播放器快照截图 支持点播多
  • Echarts 修改折线的颜色和折线的点的大小方法

    在做SPC分析的时候或者一些专业的分析的时候有的客户会要求 点的大小 样式等 具体的设置方法如下 series type 39 line 39 showSymbol true symbol 39 circle 39 设定为实心点 symbo
  • 抽象工厂模式

    抽象工厂模式针对的是对产品族 xff0c 而不是产品等级结构 产品族 xff1a 同一厂商生产的产品 产品等级 xff1a 同一产品 xff0c 不同厂商的产品 比如水果类里面由苹果和香蕉 xff0c 水果就是产品族 xff0c 苹果香蕉就
  • 面向对象单例模式

    单例模式 xff1a 一个类只能创建一个对象 span class token keyword class span span class token class name A span span class token punctuati
  • 设计模式之代理模式

    概念 xff1a 为其他对象提供一种代理 xff0c 用来控制对象的访问 在某些情况下 xff0c 一个对象不适合或不能直接引用另一个对象 xff0c 而代理对象可以在客户端和目标对象之间起到中介作用 span class token co
  • ARM接口技术基础

    ARM介绍 嵌入式系统 xff1a 嵌入式系统 61 嵌入式硬件 43 嵌入式软件 硬件是软件的载体 xff0c 软件是硬件的灵魂 嵌入式软件 xff1a 1 裸机 xff1a APP 2 系统 xff1a OS 43 APP 嵌入式硬件
  • ARM指令之MOV指令汇编与机器码的对应关系

    指令条件码 条件码就是一种简单的测试ALU状态标志位的方法 mov指令机器码 比如 xff1a mov r1 3 立即数 xff1a 3 第一个操作数 xff1a 寄存器1 S xff1a 0 xff08 注意 xff1a mov指令后面加
  • 设计模式之外观模式

    什么是外观模式 外观模式就是将一个复杂的子类系统抽象到同一个接口进行管理 外界只需要通过此接口与子类系统进行交互 xff0c 不需要直接与复杂的子类系统进行交互 外观模式属于结构型模式 外观模式案例 1 实现KTV模式 xff1a 打开电视
  • 设计模式之适配器模式

    概念 将一个类的接口转换成客户希望的另外一个接口 使得原本由于接口不兼容而不能一起工作的那些类可以一起工作 用vector容器中的for each 举例子 span class token keyword class span span c
  • Qt基础学习笔记

    Qt中的常用快捷键 注释 xff1a ctrl 43 运行 xff1a ctrl 43 r 编译 xff1a ctrl 43 b 帮助文档 xff1a F1 自动对齐 xff1a ctrl 43 i 同名之间 h文件与 cpp文件的切换 x
  • Nginx学习 (1) —— 初识Nginx(编译安装、启动停止、简单配置)

    文章目录 Nginx的发行版本Nginx的编译安装 xff08 ubuntu18 04 xff09 Nginx相关操作Nginx启动停止命令安装Nginx为系统服务 Nginx的目录结构与基本原理目录结构 xff1a 基本运行原理 xff1
  • Nginx学习 (2) —— 虚拟主机配置

    文章目录 虚拟主机原理域名解析与泛域名解析 xff08 实践 xff09 配置文件中ServerName的匹配规则技术架构多用户二级域名短网址 虚拟主机原理 为什么需要虚拟主机 xff1a 当一台主机充当服务器给用户提供资源的时候 xff0
  • 如任让Echarts默认显示全部的数据(x轴(时间轴)默认显示查询到的所有数据)

    Echarts绘制折线图 有时候会用到时间轴 但是有时候时间轴默认显示的是部分数据 而不是全部的数据 当客户要求默认显示全部的数据的时候 就可以使用下面的方法 把dataZoom属性的startValue设置为0即可 dataZoom st
  • Nginx学习(3)—— 反向代理、负载均衡、动静分离、URLRewrite、防盗链

    文章目录 网关 代理与反向代理Nginx的反向代理配置基于反向代理的负载均衡的配置负载均衡策略负载均衡权重相关配置 xff08 weight xff09 动静分离Nginx配置动静分离 URL rewrite伪静态配置负载均衡 43 URL
  • Nginx学习(4)—— 高可用配置(keepalived实现Nginx的高可用)

    文章目录 高可用场景及解决方案keepalived的安装 高可用场景及解决方案 一般高可用的服务端会都有备用服务 xff0c 当某一个服务节点挂掉的时候就会有备用机顶上 这往往需要反向代理 Nginx 来将连接切换到可用的服务机器上 如下图
  • Nginx学习(5)—— 基本结构(源码)

    文章目录 Nginx源码学习基本数据结构1 字符串结构 xff1a ngx str t2 类似资源管理的结构 xff1a ngx pool t3 Nginx数组结构 xff1a ngx array t4 哈希表结构 xff1a 1 ngx