Nginx模块开发:从源码剖析整个Nginx框架
off999 2025-01-10 16:06 12 浏览 0 评论
视频解析:
浅析nginx模块用在哪里?手把手带你实现nginx模块开发
ngx_module_t结构体
模块又分为ngx_core_module_t和ngx_xxx_module_t,而在ngx_module_t中会包含这两个结构,只不过不同类的模块包含不同的结构,一般会 用ctx表示,ngx_module_t又用type这个域用来表示在模块的类型
struct ngx_module_s {
/*ctx_index表示当前模块在这类模块中的序号。这个 成员常常是由管理模块的 一个Nginx核心模块设置的,对于所有的http模块而言,ctx_index是由核心模块ngx_http_module设置的,ctx_index非常重要,Nginx的模块化设计非常依赖各个模块的顺序*/
ngx_uint_t ctx_index;
/*index表示当前模块在ngx_modules数组中 的序号。*/
ngx_uint_t index;
char *name;
// sqare系列的 保留变量,暂未使用
ngx_uint_t spare0;
ngx_uint_t spare1;
// 模块的版本
ngx_uint_t version;
const char *signature;
/**ctx用于指向一类的模块的上下文结构体,Nginx模块有许多种类,不同模块之间的功能差别很大。例如事件模块主要处理I/O事件相关的功能,每个模块都有自己的特性,ctx将会指向特定类型模块的公共接口,例如在Http 模块 中,ctx指向ngx_http_module_t结构体*/
void *ctx;
// commands将处理nginx.conf中 的配置项
ngx_command_t *commands;
/*type 表示该模块的类型,它与ctx指针是紧密相关的,NGX_HTTP_MODULE、NGX_CORE_MODULE、NGX_CONF_MODULE、NGX_EVENT_MODULE、NGX_EVENT_MODULE、NGX_STREAM_MODULE*/
ngx_uint_t type;
/*在Nginx 启动、停止过程中,以下7个函数指针表示有7个执行点分别调用这个7个方法,对于任一方法,如果不需要Nginx在某个时刻执行 它,那么 简单地把它设为NULL空指针 即可*/
/* 虽然字面上理解应当在master进程启动时调用,但到目前为止,框架代码从来不会调用它,因此 要设为NULL*/
ngx_int_t (*init_master)(ngx_log_t *log);
/*init module 回调方法在初始化所有模块被调用,在master/worker模式下,这个阶段将在worker进程前完成 */
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
/*init_process回调方法在正常服务前被调用,在master/worker模式下,多个worker子进程已经产生,在每个worker进程初始化会调用所有模块的init_process函数*/
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
// Nginx目前还支持多线程,所以这个没有被调用过
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t *cycle);
/* exit_process回调方法在服务停止前调用。在master/worker模式下,worker会在退出前调用它*/
void (*exit_process)(ngx_cycle_t *cycle);
//该方法 会在master进程退出前调用它
void (*exit_master)(ngx_cycle_t *cycle);
//目前还没用
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;
};
这里看到有两个index,分别是ctx_index和index,他们的区别是这样子的(用http模块举例),ctx_index保存了每一个http module的config的索引,而所有的http module config是分别保存在nginx_conf_t的ctx数组中的.而index保存了每一个core module的config的索引,而每个core module的config都是保存在cycle的conf_ctx中的,下面的代码轻易看出
#define ngx_http_conf_get_module_main_conf(cf, module)
((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
#define ngx_get_conf(conf_ctx, module) conf_ctx[module.index]1234
ngx_core_module_t都包括(log, event, event_openssl, http, mail,google perftools),可以看到http module本身也是一个core module。这里要注意还有一个conf module,只不过它也是用core module这个数据结构。
typedef struct {
ngx_str_t name;
void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;
123456
ngx_http_module_t包括所有src/http/下面的模块,它就包含了所有的http module,它们都从属于http core模块。
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);
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
void *(*create_srv_conf)(ngx_conf_t *cf);
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
void *(*create_loc_conf)(ngx_conf_t *cf);
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;
还有一个ngx_module_s还有一个很重要的成员就是ngx_command_t。这个域包含当前 模块 所有包含的指令
解析完上面的数据结构,下面将从整体剖析整个Nginx框架的走向,前面的一篇博客Nginx模块自主开发六:源码剖析配置文件解析过程详细介绍了ngx_init_cycle的流程,这篇博客将主要从ngx_init_cycle如何串通所有模块 的流程说起 。(有些部分会重复,但是为了理清整个Nginx框架的流程还是值得)
需要C/C++ Linux服务器架构师学习资料后台私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
Nginx在ngx_init_cycle中通过下列代码创建所有的 configure,它 通过调用每个core module 的Create_conf的方法来创建对应的conf,每个对象都保存在全局的conf__ctx中。
/*
创建这些数据结构的工作都需要在这一步进行。Nginx框架只关心NGX_CORE_MODULE核
心模块,这也是为了降低框架的复杂度。这里将会调用所有核心模块的create conf方法(也只有核心模块才有这个方法),这意味着需要所有的核心
模块开始构造用于存储配置项的结构体。其他非核心模块怎么办呢?其实很简单。这些模块大都从属于一个核心模块,如每个HTTP模块都由ngx_http_module
管理(如图8-2所示),这样ngx_http_module在解析自己感兴趣的“http”配置项时,将会调用所有HTTP模块约定的方法来创建存储配置项的结构体
(xxx_create_main_conf、xxx_create_srv_conf、xxx_create_loc_conf方法)。
*/
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}
//得到core module
module = cycle->modules[i]->ctx;
/如果create_conf存在,则直接创建config
if (module->create_conf) {
rv = module->create_conf(cycle);
if (rv == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
cycle->conf_ctx[cycle->modules[i]->index] = rv;
}
}
.......
conf.ctx = cycle->conf_ctx; //这样下面的ngx_conf_param解析配置的时候,里面对conf.ctx赋值操作,实际上就是对cycle->conf_ctx[i]
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;
当所有的core module的config都创建完毕后,就要开始解析配置文件了,解析配置文件它会一行行读取,然后如果遇到指令,则会查找到对应的ngx_command_t对象,然后执行对应的回调set方法。这里所有动作都在ngx_conf_parse这个函数中进行。然后在ngx_conf_parse会判断cf是否有handler回调,如果有的话,优先调用handler回调,如果没有,则会进入ngx_conf_handler进行一般处理。
一般来说,如果没有自定义handler ,解析文件的过程ngx_init_cycle()->ngx_conf_parse()->ngx_conf_handler()解析 ,最后通过cmd->set方法进行回调。这时就进入我们ngx_command_t设置的 回调函数 。比如,ngx_http.c中 的ngx_http_block函数。
// 文件中的ngx_http.c的ngx_http_bock函数
// 这个函数在分配 ctx的空间之后,同时给main,serv和conf分配空间之后,初始化每个空间的配置
for (m = 0; cf->cycle->modules[m]; m++) {
if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = cf->cycle->modules[m]->ctx; // 注意module的赋值情况是ngx_module_t的成员ctx
mi = cf->cycle->modules[m]->ctx_index; //得到对应的索引
//如果有对应的回调,则调用回调函数,然后将返回的模块config设置到ctx的对应的conf列表中
if (module->create_main_conf) {
ctx->main_conf[mi] = module->create_main_conf(cf);
if (ctx->main_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
if (module->create_srv_conf) { //
ctx->srv_conf[mi] = module->create_srv_conf(cf);
if (ctx->srv_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
if (module->create_loc_conf) { //
ctx->loc_conf[mi] = module->create_loc_conf(cf);
if (ctx->loc_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
}
....
// 递归解析
rv = ngx_conf_parse(cf, NULL);
http module相关的config是在ngx_http_block中创建的,在ngx_http_block中会创建,初始化,合并config,以及整个http handler phase的初始化等等.
先是初始化所有的http module的ctx_index.
当http block完全parse完毕之后,就需要merge(main和srv或者srv和loc)相关的config了。不过在每次merge之前都会首先初始化main conf。
cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
cscfp = cmcf->servers.elts;
for (m = 0; cf->cycle->modules[m]; m++) {
if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = cf->cycle->modules[m]->ctx;
mi = cf->cycle->modules[m]->ctx_index;
/* init http{} main_conf's */
if (module->init_main_conf) {
rv = module->init_main_conf(cf, ctx->main_conf[mi]);
if (rv != NGX_CONF_OK) {
goto failed;
}
}
//然后开始merge config。
/* 每个级别都会保存对应的ctx(main/ser/loc),怎么说呢,就是在解析HTTP main中创建了3个ctx(main/srv/loc),而在HTTP srv block中将会创建2个ctx(srv/loc),这时发生重复了,那就需要merge了。比如一个命令(srv_offset)在HTTP main中有一个,那么Nginx将会把它放入到HTTP main的ctx的srv ctx中,然后server block也有一个,那么Nginx会继续把它放到Server ctx的 srv_conf中,最后merge他们。*/
rv = ngx_http_merge_servers(cf, cmcf, module, mi);//合并server{}及其以下的local{}
if (rv != NGX_CONF_OK) {
goto failed;
}
当merge完毕之后,然后就是location的处理,初始化location tree,创建handler phase,调用postconfiguration,以及变量的初始化。
/*
经过配置的读取之后,所有server都被保存在http core模块的main配置中的servers数组中,而每个server里面的location都被按配置中
出现的顺序保存在http core模块的loc配置的locations队列中,上面的代码中先对每个server的location进行排序和分类处理,这一步
发生在 ngx_http_init_location()函数中:
*/
/*location处理比较复杂*/
if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
/* 根据已经按照location字符串排序过的双向链表,快速地构建静态的二叉查找树。与ngx_http_init_locations方法类似,速个操作也是递归进行的
*/
/*
下面的ngx_http_init_static_location_trees函数就会将那些普通的location(就是ngx_http_init_locations中name noname regex以外的location(exact/inclusive)),
即staticlocation,进行树化(一种三叉树)处理,之所以要做这样的处理,是为了在处理http请求时能高效的搜索的匹配的location配置。
*/
if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
//初始化handler phase
if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
//调用回调
if (module->postconfiguration) {
if (module->postconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
//开始初始化变量
if (ngx_http_variables_init_vars(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
做完上面的工作之后,下面进行初始化socket的相关东西,比如设置读写回调 函数等
if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
/* optimize the lists of ports, addresses and server names */
if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
return NGX_CONF_ERROR;
}
ngx_cycle_init剩下的部分,当配置文件解析完毕后,就开始初始化core module的config
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}
module = cycle->modules[i]->ctx;
if (module->init_conf) {
if (module->init_conf(cycle,
cycle->conf_ctx[cycle->modules[i]->index])
== NGX_CONF_ERROR)
{
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
}
}
接下来就是创建并初始化所有创建的共享内存
if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {
goto failed;
}
if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {
goto failed;
}
if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
goto failed;
}
接下来就是开启监听,初始化所有的模块
.....
if (ngx_open_listening_sockets(cycle) != NGX_OK) {
goto failed;
}
....
// 初始化所有的模块
if (ngx_init_modules(cycle) != NGX_OK) {
/* fatal */
exit(1);
}
到时为止ngx_init_cycle()函数主要的工作都已经完成了。
总结
要搞清Nginx模块和框架之间的去协作不仅要清楚ngx_command_t结构体、ngx_module_t结构体,ngx_conf_t结构以及ngx_cycle_t结构体的关系,还要学会使用回调方法来分析整个流程 。上面见到ngx_http_block函数中的location的初始化这里 并没有仔细展开,因为mail 和stream中并没有这一步,等以后有涉及到再 详细了解。
相关推荐
- 30s带你使用Python打包exe文件,并修改其图标
-
在Python中,我们可以使用PyInstaller或cx_Freeze等工具将Python脚本打包成可执行文件(.exe),并且能够修改生成的.exe文件的图标。使用PyInstaller...
- Python一键打包为windows的exe文件,无需安装python环境即可执行
-
一、为什么要将Python打包为exe?在实际应用中,我们希望Python程序能在没有安装Python环境的电脑上直接运行。将Python代码打包为exe可执行文件,不仅能解决环境依赖问题,还便于程...
- py2exe实现python文件打包为.exe可执行程序(上篇)
-
今天分享的内容为:python程序实现发送、读取邮件来控制电脑的关机与重启(作为py2exe打包成.exe可执行程序的基础文件)一、说明:本文介绍的是使用新浪邮箱作为例子进行讲解,代码实现如下:#c...
- 如何将python程序文件打包生成一个可执行文件(exe文件)
-
在开发Python程序后,有时我们希望将其打包成一个可执行的exe文件,方便在没有Python环境的计算机上运行。下面将详细介绍使用常见工具实现这一目标的方法。安装PyInstaller...
- Python程序打包为EXE的全面指南:从入门到精通
-
引言在Python开发中,将程序打包成可执行文件(EXE)是分发应用程序的重要环节。通过打包,我们可以创建独立的可执行文件,让没有安装Python环境的用户也能运行我们的程序。本篇文章将详细介绍如何使...
- 10个你没有充分利用的令人惊叹的 Python 特性
-
Python的简单性和多功能性使其成为全球开发人员的最爱。每天有超过1000万开发者使用Python进行从网络开发、机器学习到网络脚本等各种开发,Python的功能非常强大。然而,我们中的...
- 编程语言可以用来做什么
-
1.web前端你每天浏览的网页,所看到的页面特效,均是由web前端工程师来实现的2.Java大型购物网站有关通信及网络企业大型企业级应用管理系统大型网游后台数据3.C++嵌入式三维游戏领域人工智能领域...
- 用Python进行机器学习(16)-内容总结
-
对于用Python进行机器学习的内容,到这里就要做一个阶段性总结啦,后续再写的文章就是关于深度学习的了,算是对该部分内容的进阶版。对于机器学习,我们主要介绍了五个方面的内容:第一个就是分类算法,主要包...
- 普通人如何利用python做自媒体赚收益
-
普通人利用Python做自媒体赚收益,最简单的方式是下载某些网站的视频,并利用剪影编辑视频,最后导出发布,每天可以制作个10几条,并设置好定时发布,每天如此坚持下去,一定会有所收获的...
- AI能写什么做什么?这些技能已经颠覆你的认知!
-
在ChatGPT、文心一言等AI工具爆火的今天,人工智能早已不再是科幻电影里的概念,而是实实在在地渗透进我们的生活。**AI到底能写什么?能做什么?它的边界在哪里?**让我们一探究竟!---**1....
- Python 3.14 新特性盘点,更新了些什么?
-
Python3.14.0稳定版将于2025年10月正式发布,目前已进入beta测试阶段。这意味着在往后的几个月里,3.14的新功能已冻结,不再合入新功能(除了修复问题和完善文档)。3...
- 每天一个Python库:sys模块的5个高频用法(建议收藏)
-
很多人学Python,一直卡在“写不了实用脚本”。其实,会用标准库,效率直接翻倍。今天分享的是:sys模块。这个模块虽然基础,但非常实用,下面是我亲测常用的5个功能1.获取命令行参数(自动化脚...
- Python除了做爬虫抓数据还能做什么?其实还能监视和衡量网站性能
-
借助这份对初学者友好的指南,您可以构建自己的自定义Python脚本来自动测量网站的关键速度和性能指标。 在过去的一个月中,Google宣布了许多通过关键速度和性能指标来衡量用户体验的方法。 巧...
- python究竟可以用来做些什么
-
这里就不撰述python的一些像什么“高级语言”之类的比较常规的介绍了,还是老样子,说说一些比较常用的东西吧。python是什么python,一款可编程的开源软件,很多第三方库、框架也是开源的,比如强...
- Python 实现 dubbo 协议接口自动化测试
-
前言python语言也可以实现对dubbo协议的接口进行调用与测试,可以使用python+hessian结合的方式,也可以使用python+telnet结合的方式模拟命令行的模式来实现对...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- python计时 (54)
- python安装路径 (54)
- python类型转换 (75)
- python进度条 (54)
- python的for循环 (56)
- python串口编程 (60)
- python写入txt (51)
- python读取文件夹下所有文件 (59)
- java调用python脚本 (56)
- python操作mysql数据库 (66)
- python字典增加键值对 (53)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python qt (52)
- python人脸识别 (54)
- python斐波那契数列 (51)
- python多态 (60)
- python命令行参数 (53)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- centos7安装python (53)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)