Nginx专题(二、nginx常用的数据结构和把自己模块编译进nginx)

这一节,我们主要是了解nginx基本的数据结构和http模块中使用的一些数据结构,并且把自己编写的源码编译进nginx中。

2.1 简单的数据结构

nginx为了兼容多平台都会对数据进行再一次的封装,下来看看一些比较简单的封装。

2.1.1 整形的封装

ngx_init_t 封装有符号整形,使用ngx_uint_t封装无符号整形。

在linux平台下,nginx对ngx_init_t和ngx_uint_t的定义如下:

typedef intptr_t 	ngx_init_t;
typedef uintptr_t 	ngx_uint_t;

2.1.2 ngx_str_t数据结构

在nginx的领域中,ngx_str_t结构就是字符串。ngx_str_t的定义如下:

typedef struct {
	size_t 		len;
	u_char 		*data;
}

ngx_str_t只有两个成员,其中data指针指向字符串起始地址,len表示字符串的有效长度。注意:ngx_str_t的data成员指向的并不是普通的字符串,因为这段字符串未必会以’\0’作为结尾,所以使用时必须根据长度len来使用data成员。

nginx提供了一个字符串拷贝函数:

ngx_strncmp(r->method_name.data, “PUT”, r->method_name.len);

注意:ngx_strncmp其实就是strncmp函数,为了跨平台nginx习惯性地对其封装,下面看一下它的定义:

#define ngx_strncmp(s1, s2, n) strncmp((const char *)s1, (const char *)s2, n)

2.1.3 ngx_list_t数据结构

ngx_list_t是nginx封装的链表容器,先看一下ngx_list_t相关成员定义:

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;		//下一个链表元素ngx_list_part_t的地址
};

typedef struct  {
    ngx_list_part_t		*last;		//指向链表的最后一个数组元素
    ngx_list_part_t		part;		//链表的首个数组元素
    size_t 				size;		//size限制每一个数组元素的占用的空间大小,也就是用户要存储的一个数据所占用的字节数必须小于或等于size
    ngx_uint_t			nalloc;		//表示每个ngx_list_part_s数组的容量,即最多可存储多少个数据
    ngx_pool_t			*pool;		//链表中管理内存分配的内存池对象
} ngx_list_t;

ngx_list_t描述整个链表,而ngx_list_part_s只描述链表的一个元素,每一个ngx_list_part_s又是一个数组,拥有连续的内存,elts指针就是指向数据的起始地址,nalloc是表示需要存储多少个数据,size表示一个数组元素的大小,所以elts需要分配的内存是nalloc*size字节。

下面来看看ngx_list_t的内存分布情况:

在这里插入图片描述

对于链表nginx也提供了接口:

/*
	创建链表
	pool : nginx内存池
	n : 每个链表数组可容纳n个元素
	size : 每个元素的大小
*/ 
ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);

/*
	初始化链表
	list:分配好内存的ngx_list_t链表
	pool:nginx内存池
	n:每个链表数组可容纳n个元素
	size : 每个元素的大小
*/
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:创建好的ngx_list_t链表
*/
void *ngx_list_push(ngx_list_t *list);			

创建的函数应该比较简单,这里就不写了,但是添加函数跟别的实现不太一样,这里可以讲述一下:

ngx_list_push在正常情况下,返回的是新分配的元素首地址。如果返回NULL空指针,则表示添加失败。

使用方式如下:

//先申请一个元素,返回新分配元素的首地址
ngx_str_t  *str = ngx_list_push(testlist);
if(str == NULL)  {
	return ERROR;
}

//进行赋值
str->len = sizeof("Hello world");
str->data = "Hello world";

2.1.4 ngx_table_t数据结构

结构如下:

typedef struct {
    ngx_uint_t 			hash;			//ngx_hash_t中的hash
    ngx_str_t			key;			//key
    ngx_str_t			value;
    u_char 				*lowcase_key;	//全小写的key字符串
} ngx_table_elt_t;

ngx_table_elt_t就是一个key/value对,key的值存储的是名字,value是值字符串;hash成员表面ngx_table_elt_t也可以是某个散列表数据结构(ngx_hash_t类型)中的成员。ngx_uint_t类型的hash成员可以在ngx_hash_t更快地找到相同key的ngx_table_elt_t数据。lowcase_key指向的是全小写的key字符串。

2.1.5 ngx_buf_t数据结构

缓冲区ngx_buf_t是nginx处理大数据的关键数据结构,它可以用在内存数据也可以应用在磁盘数据。

下面介绍ngx_buf_t的数据结构:

typedef void *            ngx_buf_tag_t;

typedef struct ngx_buf_s  ngx_buf_t;

struct ngx_buf_s {
    u_char          *pos;			/* 通常用来告诉使用这本次应该从pos这个位置开始处理内存中的数据 */
    u_char          *last;			/* last通常表示有效的内容到此为止 */
    off_t            file_pos;		/* 处理文件的时候,开始的地址 */
    off_t            file_last;		/* 处理文件的时候,结束的地址 */

    u_char          *start;         /* 如果ngx_buf_t缓冲区用于内存,那么start指向这段内存的起始地址 */
    u_char          *end;           /* 与start成员对应,指向缓冲区内存的末尾 */
    ngx_buf_tag_t    tag;			/* 表示当前缓冲区的类型,例如由哪个模块使用就指向这个模块ngx_module_t变量地址 */
    ngx_file_t      *file;			/* 引用文件 */
    ngx_buf_t       *shadow;		/* 不建议使用 */


    /* the buf's content could be changed */
    unsigned         temporary:1;	//临时内存标志位,为1时表示数据在内存中且这段内存可以修改

    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;		//标志位,为1时表示数据在内存中且这段内存不可以修改

    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;	//标志位,为1时表示这段内存是用mmap系统调用映射过来的,不可以修改

    unsigned         recycled:1;	//标志位,为1时表示可回收
    unsigned         in_file:1;		//标志位,为1时表示这段缓冲区处理的是文件而不是内存
    unsigned         flush:1;		//标志位,为1时表示需要执行flush操作
    unsigned         sync:1;		//标志位,对于操作这块缓冲区时是否使用同步方法,谨慎使用,可以会造成nginx阻塞	
    unsigned         last_buf:1;	//标志位,表示是否是最后一块缓冲区
    unsigned         last_in_chain:1;	//标志位,表示是否是ngx_chain_t中的最后一块缓冲区

    unsigned         last_shadow:1;		//标志位,表示是否是最后一个影子缓冲区,通常不建议使用
    unsigned         temp_file:1;		//标志位,表示当前缓冲区是否属于临时文件

    /* STUB */ int   num;
};

ngx_buf_t是一种基本数据结构,本质上就是提供一些指针成员和标志位。但对于HTTP协议来说,还是需要了解一些这些遍历的值和意义。

2.1.6 ngx_chain_t数据结构

ngx_chain_t是与ngx_buf_t配合使用的链表数据结构,下面看一下它的定义:

struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};

buf指向当前的ngx_buf_t缓冲区,next则用来指向下一个ngx_chain_t。如果这是最后一个ngx_chain_t,则需要把next置为NULL。

2.2 自己写的模块编译进nginx

nginx提供了2种把自己写的代码编译进nginx的方法,这里我就先写一种

2.2.1 config文件

config文件其实是一个可执行的shell脚本,如果要自己编写HTTP模块,就可以自己添加一个config文件,进行添加进nginx中。

2.2.1.1 添加config文件

就不把我们创建的模块的源码放到nginx源码中,自己创建一个目录,这样也方便管理。

在这里插入图片描述

在nginx目录的文件夹上,创建一个mytest_module的目录,存放我们自己写的模块。

在这里插入图片描述

mytest文件夹中存放了一个config的执行脚本,还有就是mytest_module的源码。

2.2.1.2 config编写

这个config文件的内容就是告诉nginx的编译脚本,该如何编译。如果想开发一个HTTP模块,就需要在config文件中定义如下3个变量:

  • ngx_addon_name :仅在configure执行时使用,一般设置为模块名称
  • HTTP_MODULES:保存所有的HTTP模块名称,每个HTTP模块间由空格符相连。
  • NGX_ADDON_SRCS:用于指定新增模块的源代码,多个待编译的源代码间以空格符相连。注意:在设置NGX_ADDON_SRCS时可以使用$ngx_addon_dir变量,它等价于configure执行时–add-module=PATH的PATH参数。

实际上的config文件的内容:

ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"

2.2.1.3 编译

对于模块的编译,nginx并不像Apache一样,提供了单独的编译工具,可以在没有Apache源代码的情况下单独编译一个模块代码。nginx必须要到nginx的源代码里面,通过configure指令参数,来进行编译。下面就以testmodule为例,进行编译的命令:

./configure --add-module=/root/cloud_disk/04_nginx/mytest_module

看到这个命令就懂了吧,主要把文件目录作为configure的参数传入即可。

在这里插入图片描述

接下来就试着运行这条配置信息,果然就把我们加的模块添加进来了。

2.2.2 直接修改makefile

省略

2.3 HTTP模块的数据结构

HTTP模块的数据结构

2.3.1 ngx_module_s

struct ngx_module_s {
    ngx_uint_t            ctx_index;    //对于一类模块来说,ctx_index表示当前模块在这类模块中的序号。
    ngx_uint_t            index;	//index表示当前模块ngx_modules数组中的序号。跟ctx_index的区别是这是所有模块的下标

    char                 *name;		//模块的名字

    //spare0保留变量
    ngx_uint_t            spare0;
    ngx_uint_t            spare1;

    ngx_uint_t            version;	//模块的版本,便于将来的扩展。目前只有一种,默认为1
    const char           *signature; //签名?

    void                 *ctx;		//ctx用于指向一类模块的上下文的结构体,就是需要挂载一个自己模块的变量,http模块的话,要指向ngx_http_module_t结构体
    ngx_command_t        *commands;	//command将处理nginx.conf中的配置项
    ngx_uint_t            type;	//表示该模块的类型,官方有5种类型:NGX_HTTP_MODULE、NGX_CORE_MODULE、NGX_CONF_MODULE、NGX_EVENT_MODULE、NGX_MAIL_MODULE,也可以自己定义新类型

    //7个函数指针,代表这nginx启动停止过程中的,钩子函数
    
    ngx_int_t           (*init_master)(ngx_log_t *log);  //理解为master进程启动时回调init_master,但到目前为止,框架代码从来不调用它

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);		//初始化所有模块时被调用,在master/worker模式下,这个阶段在启动worker子进程前完成

    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);  //在正常服务前被调用,在master/worker模式下,多个worker子进程已经产生,每个worker进程的初始化过程会调用所有模块的init_process
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);		//由于nginx暂不支持多线程模式,所以这个还没使用
    void                (*exit_thread)(ngx_cycle_t *cycle);   //同时。不支持线程
    void                (*exit_process)(ngx_cycle_t *cycle);	//服务停止调用前调用,在master/worker模式下,worker进程会在进程退出前调用它

    void                (*exit_master)(ngx_cycle_t *cycle);		//将在master进程退出前被调用

    //以下都是保留字段,目前没有被使用
    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

通过分析发现,这个ngx_module_t结构体中有两个重要的成员,ctx和commands,ctx是指向不同模块的不同结构体,下面我们先分析HTTP模块的数据结构,ngx_http_module_t

2.3.2 ngx_http_module_t

typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);	//解析配置文件前调用
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);	//完成配置文件的解析后调用

    void       *(*create_main_conf)(ngx_conf_t *cf);	//当需要创建数据结构用于储存main级别的全局变量时,可以通过这个回调方法创建存储全局配置项的结构体
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);  //初始化main级别配置项

    void       *(*create_srv_conf)(ngx_conf_t *cf); //当需要创建数据结构用于储存srv级别的变量时,可以通过这个回调方法创建存储配置项的结构体
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);  //主要用于合并main级别和srv级别下的同名配置项

    void       *(*create_loc_conf)(ngx_conf_t *cf);		//当需要创建数据结构用于储存loc级别的变量时,可以通过这个回调方法创建存储配置项的结构体
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);	//主要是合并srv级别和loc级别下的同名配置项
} ngx_http_module_t;

想不到http module都是一些钩子函数,确实有点惊讶,不过nginx的架构设计很好,在每一个点都设计了一个钩子函数,可以交给用户自己去使用。

但是这8个阶段的调用顺序与上述定义的顺序是不同的,在nginx启动过程中,HTTP框架调用这些回调方法的实际顺序可能是这样的:

1) creat_main_conf
2) creat_srv_conf
3) creat_loc_conf
4) preconfiguration
5) init_main_conf
6) merge_srv_conf
7) merge_loc_conf
8) postconfiguration

2.3.3 ngx_command_s

command数组用于定义模块配置文件参数,每一个数组元素都是ngx_command_t类型,数组的结尾用ngx_null_command表示。nginx会遍历这些数组元素,等待遍历到ngx_null_command的时候,就知道遍历已经结束。

struct ngx_command_s {
    ngx_str_t             name;		//配置项名称,如"gzip"
    ngx_uint_t            type;		//配置项的类型,type将指定配置项可以出现的位置
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);	//出现了name中指定的配置项后,将会调用set方法处理配置项的参数
    ngx_uint_t            conf;		//在配置文件中的偏移量
    ngx_uint_t            offset;	//需要与conf配置,下一节解析
    void                 *post;		//配置项读取后的处理方法,必须是ngx_conf_post_t结构的指针
};

//ngx_null_command只是一个空的ngx_command_t,如下所示:
#define ngx_null_command  { ngx_null_string, 0, NULL, 0, 0, NULL }

想不到nginx的整体数据结构看起来是比较整洁的,确实不错。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值