Nginx Rtmp Learing 之基本数据结构ngx_module_t
1. ngx_module_t的基本结构
对于开发一个模块来说,我们都需要定义一个ngx_module_t
类型的变量来说明这个模块本身的信息,从某种意义上来说,这是这个模块最重要的一个信息,它告诉了nginx这个模块的一些信息,配置信息,还有模块上下文信息,都是通过这个结构来告诉nginx系统的,也就是加载模块的上层代码,都需要通过定义的这个结构,来获取这些信息。
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
// 下面的几个成员通常使用宏NGX_MODULE_V1填充
// 每类(http/event)模块各自的index, 初始化为-1
ngx_uint_t ctx_index;
// 在ngx_modules数组里的唯一索引,main()里赋值
// 使用计数器变量ngx_max_module
ngx_uint_t index;
// 1.10,模块的名字,标识字符串,默认是空指针
// 由脚本生成ngx_module_names数组,然后在ngx_preinit_modules里填充
// 动态模块在ngx_load_module里设置名字
char *name;
// 两个保留字段,1.9之前有4个
ngx_uint_t spare0;
ngx_uint_t spare1;
// nginx.h:#define nginx_version 1010000
ngx_uint_t version;
// 模块的二进制兼容性签名,即NGX_MODULE_SIGNATURE
const char *signature;
// 模块不同含义不同,通常是函数指针表,是在配置解析的某个阶段调用的函数
// core模块的ctx
//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;
void *ctx;
// 模块支持的指令,数组形式,最后用空对象表示结束
ngx_command_t *commands;
// 模块的类型标识,相当于RTTI,如CORE/HTTP/STRM/MAIL等
ngx_uint_t type;
// 以下7个函数会在进程的启动或结束阶段被调用
// init_master目前nginx不会调用
ngx_int_t (*init_master)(ngx_log_t *log);
// 在ngx_init_cycle里被调用
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
// 在ngx_single_process_cycle/ngx_worker_process_init里调用
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
// init_thread目前nginx不会调用
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
// exit_thread目前nginx不会调用
void (*exit_thread)(ngx_cycle_t *cycle);
// 在ngx_worker_process_exit调用
void (*exit_process)(ngx_cycle_t *cycle);
// 在ngx_master_process_exit(os/unix/ngx_process_cycle.c)里调用
void (*exit_master)(ngx_cycle_t *cycle);
// 下面8个成员通常用用NGX_MODULE_V1_PADDING填充
// 暂时无任何用处
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;
};
2. ngx_modules数组
2.1 ngx_modules数组
ngx_modules
数组是在执行configure脚本后自动生成的,在objs/ngx_modules.c文件中。该数组即当前编译版本中的所有Nginx模块。
如果想让nginx支持Rtmp的话,需要在执行configure脚本时添加第三方库nginx-rtmp-module
./configure --add-module=/path/to/nginx-rtmp-module
此时,objs/ngx_modules.c的内容为:
ngx_module_t *ngx_modules[] = {
&ngx_core_module,
&ngx_errlog_module,
&ngx_conf_module,
&ngx_rtmp_module,
&ngx_rtmp_core_module,
&ngx_rtmp_cmd_module,
&ngx_rtmp_billing_module,
&ngx_rtmp_codec_module,
&ngx_rtmp_access_module,
&ngx_rtmp_record_module,
&ngx_rtmp_live_module,
&ngx_rtmp_hdl_module,
&ngx_rtmp_hls_delay_module,
&ngx_rtmp_hls_vod_module,
&ngx_rtmp_hls_module,
&ngx_rtmp_play_module,
&ngx_rtmp_flv_module,
&ngx_rtmp_mp4_module,
&ngx_rtmp_netcall_module,
&ngx_rtmp_relay_module,
&ngx_rtmp_exec_module,
&ngx_rtmp_auto_push_module,
&ngx_rtmp_notify_module,
&ngx_rtmp_listener_init_module,
&ngx_rtmp_log_module,
&ngx_rtmp_limit_module,
&ngx_rtmp_dash_module,
&ngx_openssl_module,
&ngx_regex_module,
&ngx_events_module,
&ngx_event_core_module,
&ngx_epoll_module,
&ngx_http_module,
&ngx_http_core_module,
&ngx_http_log_module,
&ngx_http_upstream_module,
&ngx_http_static_module,
&ngx_http_autoindex_module,
&ngx_http_index_module,
&ngx_http_auth_basic_module,
&ngx_http_access_module,
&ngx_http_limit_conn_module,
&ngx_http_limit_req_module,
&ngx_http_geo_module,
&ngx_http_map_module,
&ngx_http_split_clients_module,
&ngx_http_referer_module,
&ngx_http_rewrite_module,
&ngx_http_proxy_module,
&ngx_http_fastcgi_module,
&ngx_http_uwsgi_module,
&ngx_http_scgi_module,
&ngx_http_memcached_module,
&ngx_http_empty_gif_module,
&ngx_http_browser_module,
&ngx_http_upstream_hash_module,
&ngx_http_upstream_ip_hash_module,
&ngx_http_upstream_least_conn_module,
&ngx_http_upstream_keepalive_module,
&ngx_http_upstream_zone_module,
&ngx_rtmp_http_hls_module,
&ngx_rtmp_http_hdl_module,
&ngx_rtmp_stat_module,
&ngx_rtmp_control_module,
&ngx_http_write_filter_module,
&ngx_http_header_filter_module,
&ngx_http_chunked_filter_module,
&ngx_http_range_header_filter_module,
&ngx_http_gzip_filter_module,
&ngx_http_postpone_filter_module,
&ngx_http_ssi_filter_module,
&ngx_http_charset_filter_module,
&ngx_http_userid_filter_module,
&ngx_http_headers_filter_module,
&ngx_http_copy_filter_module,
&ngx_http_range_body_filter_module,
&ngx_http_not_modified_filter_module,
NULL
};
如上所述,ngx_modules
数组代表了当前Nginx中的所有模块。每一个Nginx模块,即ngx_module_t
类型的变量,其ctx变量一般为如下几个类型之一:
ngx_core_module_t
ngx_event_module_t
ngx_http_module_t
ngx_mail_module_t
所以,可以对所有的Nginx模块按其ctx类型进行分类,当然并不是所有的nginx模块都有ctx成员,例如ngx_conf_module
模块的ctx成员为NULL,但可以认为绝大部分模块都属于上述四类模块之一。
每一类模块下的所有模块,由于其ctx结构是一样的,因而在程序执行逻辑上会有共同点。具体来说,我们可以遍历ngx_modules数组成员,判断其属于上述四类的哪一类模块,再调用其对应的ctx成员中的函数指针,这样就会屏蔽掉具体的模块名称,抽象到框架的层面上。
那么,如何判断一个模块属于上述四类模块中的哪一类呢?这就是模块变量的type成员的作用了。
所有的ngx_core_module_t类型的模块,其type成员为NGX_CORE_MODULE
所有的ngx_event_module_t类型的模块,其type成员为NGX_EVENT_MODULE
所有的ngx_http_module_t类型的模块,其type成员为NGX_HTTP_MODULE
所有的ngx_mail_module_t类型的模块,其type成员为NGX_MAIL_MODULE
所以在遍历ngx_modules
数组时,即可根据每一个数组成员的type成员,来判断该模块属于哪种类型的模块,进而执行该模块对应的钩子函数。
2.2 ngx_module_t的分层模块结构
Nginx 采用了分层模块结构设计,顶层模块由 Nginx 自身管理。比如, 在 ngx_init_cycle
有一段代码为:
ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
ngx_uint_t i, n;
// ...
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}
module = cycle->modules[i]->ctx;
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;
}
}
// ...
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;
}
}
}
// ...
return NULL;
}
类型为 NGX_CORE_MODULE
的模块的 create_conf
和 init_conf
会被调用。 类型为 NGX_CORE_MODULE
的摸块即属于 Nginx 中的顶层模块。 顶层模块除负责像日志打印、配置解析等核心业务外,也需要管理二级模块的接入。 实际上,上层模块负责下层模块的接入,这是一种很自然的设计。 假如,我们自己在 Nginx 中扩展了二级模块,而由于业务复杂,我们需要进一步进行模块划分。 而新划分出的模块则属于三级模块,那这三级模块的接入不由我们自己定义的二级模块接入又该由谁负责呢?
rtmp模块的类型为 NGX_RTMP_MODULE
,Nginx中与rtmp相关的模块有: ngx_rtmp_module
, ngx_rtmp_core_module
, ngx_rmtp_***_module
。 其中 ngx_rtmp_module
属于顶层模块,但它负责二级事件模块的接入; ngx_rtmp_core_module
是核心的事件模块,它负责server,application配置文件的解析和管理、事件的挂载、初始化listen等; ngx_rtmp_**_module
是nginx下的其它所有的rtmp模块。 我们来ngx_rtmp_module这个顶层模块的实现:
2.3 ngx_rtmp_module(ngx_rtmp.c文件)
2.3.1 ngx_rtmp_module
的定义
ngx_module_t ngx_rtmp_module = {
NGX_MODULE_V1,
&ngx_rtmp_module_ctx, /* module context */
ngx_rtmp_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
ngx_rtmp_init_process, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
2.2.2 ngx_rtmp_module的上下文(ngx_rtmp_module_ctx)
// 核心模块的ctx结构,比较简单,只有创建和初始化配置结构函数 .
// create_conf函数返回的是void*指针
static ngx_core_module_t ngx_rtmp_module_ctx = {
ngx_string("rtmp"), /*name*/
NULL, /*create_conf*/
NULL /*init_conf*/
};
2.2.3 模块配置指令(ngx_rtmp_commands)
// 指令结构体,用于定义nginx指令
static ngx_command_t ngx_rtmp_commands[] = {
{ ngx_string("rtmp"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_rtmp_block,
0,
0,
NULL },
ngx_null_command
};
看这个定义,基本能看出来一些信息。例如,我们是定义了一个配置指令叫rtmp,不接受任何参数。
接下来看下ngx_command_t
的定义,位于src/core/ngx_conf_file.h
中。
struct ngx_command_s {
ngx_str_t name;
ngx_uint_t type;
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_uint_t conf;
ngx_uint_t offset;
void *post;
};
name: 配置指令的名称。
type: 该配置的类型,其实更准确一点说,是该配置指令属性的集合。
set: 这是一个函数指针,当nginx在解析配置的时候,如果遇到这个配置指令,将会把读取到的值传递给这个函数进行分解处理。因为具体每个配置指令的值如何处理,只有定义这个配置指令的人是最清楚的。来看一下这个函数指针要求的函数原型。
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
先看该函数的返回值,处理成功时,返回NGX_OK
,否则返回NGX_CONF_ERROR
或者是一个自定义的错误信息的字符串。
再看一下这个函数被调用的时候,传入的三个参数。
- cf: 该参数里面保存从配置文件读取到的原始字符串以及相关的一些信息。特别注意的是这个参数的args字段是一个
ngx_str_t
类型的数组,该数组的首个元素是这个配置指令本身,第二个元素是指令的第一个参数,第三个元素是第二个参数,依次类推。 - cmd: 这个配置指令对应的
ngx_command_t
结构。 - conf: 就是定义的存储这个配置值的结构体。用户在处理的时候可以使用类型转换,转换成自己知道的类型,再进行字段的赋值。
// 空指令,用于在指令数组的最后当做哨兵,结束数组,避免指定长度,类似NULL的作用
#define ngx_null_command { ngx_null_string, 0, NULL, 0, 0, NULL }
2.3 ngx_rtmp_block配置解析函数
这个函数可以理解为整个ngx_rtmp_module
的入口函数:
// 解析rtmp{}配置块,里面有server{}/application{}等
// 只有出现这个指令才会在conf_ctx里创建rtmp配置,避免内存浪费
// 统计rtmp模块的数量,设置rtmp模块的ctx_index,即rtmp模块自己的序号
// 调用每个rtmp模块的create_xxx_conf函数,创建配置结构体
// 初始化rtmp处理引擎的阶段数组,调用ngx_array_init
// 整理所有的rtmp handler模块,填入引擎数组
// 调用ngx_create_listening添加到cycle的监听端口数组,只是添加,没有其他动作
// 设置有连接发生时的回调函数ngx_rtmp_init_connection
static char *
ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
ngx_uint_t m, mi, s;
ngx_conf_t pcf;
ngx_rtmp_module_t *module;
ngx_rtmp_conf_ctx_t *ctx;
ngx_rtmp_core_srv_conf_t *cscf, **cscfp;
ngx_rtmp_core_main_conf_t *cmcf;
/* the main rtmp context */
// ngx_rtmp_conf_ctx_t里有三个void*数组,存储三个层次的模块配置
// in ngx_rtmp.h
//
// typedef struct {
// void **main_conf;
// void **srv_conf;
// void **loc_conf;
// } ngx_rtmp_conf_ctx_t;
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
*(ngx_rtmp_conf_ctx_t **) conf = ctx;
/* count the number of the rtmp modules and set up their indices */
// 统计rtmp模块的数量
// 设置rtmp模块的ctx_index,即rtmp模块自己的序号
ngx_rtmp_max_module = 0;
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
ngx_modules[m]->ctx_index = ngx_rtmp_max_module++;
}
/* the rtmp main_conf context, it is the same in the all rtmp contexts */
// main配置数组,所有rtmp模块只有一个
ctx->main_conf = ngx_pcalloc(cf->pool,
sizeof(void *) * ngx_rtmp_max_module);
if (ctx->main_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* the rtmp null srv_conf context, it is used to merge
* the server{}s' srv_conf's
*/
// srv配置数组,在rtmp main层次存储server基本的配置,用于合并
// 本身并无实际意义
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* the rtmp null app_conf context, it is used to merge
* the server{}s' app_conf's
*/
// location配置数组,在rtmp main层次存储location基本的配置,用于合并
// 本身并无实际意
ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
if (ctx->app_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* create the main_conf's, the null srv_conf's, and the null app_conf's
* of the all rtmp modules
*/
// 调用每个rtmp模块的create_xxx_conf函数,创建配置结构体
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
mi = ngx_modules[m]->ctx_index;
// 创建每个模块的main_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;
}
}
// 创建每个模块的srv_conf
if (module->create_srv_conf) {
ctx->srv_conf[mi] = module->create_srv_conf(cf);
if (ctx->srv_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
// 创建每个模块的loc_conf
if (module->create_app_conf) {
ctx->app_conf[mi] = module->create_app_conf(cf);
if (ctx->app_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
}
// 初始的解析环境已经准备好,下面开始解析rtmp{}配置
// 暂存当前的解析上下文
// cf是函数入口传递来的上下文
pcf = *cf;
// 设置事件模块的新解析上下文
// 即ngx_http_conf_ctx_t结构体
cf->ctx = ctx;
// 解析之前,调用preconfiguration,可以添加变量定义
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
if (module->preconfiguration) {
if (module->preconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
/* parse inside the rtmp{} block */
// 设置解析的类型等信息
// NGX_HTTP_MODULE用来检查是否是http模块,防止用错了指令
cf->module_type = NGX_RTMP_MODULE;
cf->cmd_type = NGX_RTMP_MAIN_CONF;
// 递归解析rtmp模块
rv = ngx_conf_parse(cf, NULL);
if (rv != NGX_CONF_OK) {
*cf = pcf;
return rv;
}
/* init rtmp{} main_conf's, merge the server{}s' srv_conf's */
// 解析完毕,检查rtmp{}里定义的server{}块
cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];
// 所有server{}定义的配置都保存在core module main conf的serevrs数组里
cscfp = cmcf->servers.elts;
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
mi = ngx_modules[m]->ctx_index;
/* init rtmp{} main_conf's */
cf->ctx = ctx;
// 初始化main配置
if (module->init_main_conf) {
rv = module->init_main_conf(cf, ctx->main_conf[mi]);
if (rv != NGX_CONF_OK) {
*cf = pcf;
return rv;
}
}
for (s = 0; s < cmcf->servers.nelts; s++) {
/* merge the server{}s' srv_conf's */
cf->ctx = cscfp[s]->ctx;
// 合并srv配置
if (module->merge_srv_conf) {
rv = module->merge_srv_conf(cf,
ctx->srv_conf[mi],
cscfp[s]->ctx->srv_conf[mi]);
if (rv != NGX_CONF_OK) {
*cf = pcf;
return rv;
}
}
if (module->merge_app_conf) {
/* merge the server{}'s app_conf */
/*ctx->app_conf = cscfp[s]->ctx->loc_conf;*/
rv = module->merge_app_conf(cf,
ctx->app_conf[mi],
cscfp[s]->ctx->app_conf[mi]);
if (rv != NGX_CONF_OK) {
*cf = pcf;
return rv;
}
/* merge the applications{}' app_conf's */
cscf = cscfp[s]->ctx->srv_conf[ngx_rtmp_core_module.ctx_index];
rv = ngx_rtmp_merge_applications(cf, &cscf->applications,
cscfp[s]->ctx->app_conf,
module, mi);
if (rv != NGX_CONF_OK) {
*cf = pcf;
return rv;
}
}
}
}
if (ngx_rtmp_init_events(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
// 执行每个rtmp模块的postconfiguration函数指针
//** 通常是向phase数组里添加handler**
if (module->postconfiguration) {
if (module->postconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
// 恢复之前保存的解析上下文
*cf = pcf;
if (ngx_rtmp_init_event_handlers(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
/* optimize the lists of ports, addresses and server names */
// 类似stream模块,对已经整理好的监听端口数组排序
// 调用ngx_create_listening添加到cycle的监听端口数组,只是添加,没有其他动作
// 设置有连接发生时的回调函数ngx_rtmp_init_connection
return ngx_rtmp_optimize_servers(cf, cmcf, &cmcf->ports);
}
参考文章:
https://github.com/chronolaw/annotated_nginx
http://tengine.taobao.org/book/