rtmp模块初始化源码剖析
rtmp模块初始化过程主要分为以下几个点: 上下文结构初始化、配置项解析、配置项合并、server 相关端口设置。相关源码文件主要在ngx_rtmp.c和ngx_rtmp.h文件中
下面主要从rtmp框架相关的数据结构,以及整个初始化过程进行分析。
数据结构
nginx-rtmp模块的核心框架定义的通用数据结构主要有以下几个:ngx_rtmp_module_t 、ngx_rtmp_conf_ctx_t
nginx中定义了模块的通用接口ngx_module_t。而对于每个不同类型的模块,又有一个具体的结构体来描述这一类模块的接口,例如:http,event模块,mail等.
而ngx_rtmp_module_t是nginx-rtmp项目模块的rtmp类型的接口。和http 类似,rtmp把rtmp{}, server{}, app{}块的配置项分别称为main、srv、app 级别配置项
typedef struct {
/* 解析rtmp{} 块内的配置项之前的回调 */
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
/* 解析rtmp{} 块内的配置项之后的回调 */
ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
/* 用于创建rtmp块内main级别的配置 */
void *(*create_main_conf)(ngx_conf_t *cf);
/* 解析完main配置项后回调 */
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
/*
* 创建用于同时出现在main、srv级别配置项的结构体;
* 该结构体中的成员与server配置是相关联的;
*/
void *(*create_srv_conf)(ngx_conf_t *cf);
/*
* 由create_srv_conf产生的结构体所要解析的配置项,
* 可能同时出现在main、srv级别中,
* merge_srv_conf 方法可以将出现在main级别中的配置项值合并到srv级别的配置项中;
*/
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
void *conf);
/*
* 创建用于同时出现在main、srv、app级别配置项的结构体,
* 该结构体成员与app级别配置相关联;
*/
void *(*create_app_conf)(ngx_conf_t *cf);
/*
* merge_app_conf 产生的结构体所要解析的配置项,
* 可能同时出现在main、srv、app级别的配置项中,
* merge_loc_conf 方法将出现在main、srv级别的配置项值合并到loc级别的配置项中;
*/
char *(*merge_app_conf)(ngx_conf_t *cf, void *prev,
void *conf);
} ngx_rtmp_module_t;
在 RTMP 模块中,管理 RTMP 模块配置项的结构由 ngx_rtmp_conf_ctx_t 实现,该结构有三个成员,分别指向三个指针数组,指针数组是由相应地 RTMP 模块create_main_conf、create_srv_conf、create_app_conf 方法创建的结构体指针组成的数组
typedef struct {
/*
* 数组中的每个成员都是由所有RTMP模块create_main_conf方法创建的存放全局配置项的结构体,
* 它们存放着解析直属于rtmp{}块内main级别的配置项参数;
*/
void **main_conf;
/*
* 数组中的每个成员都是由所有RTMP模块create_srv_conf方法创建的与server相关的配置项结构体,
* 它们存放着main级别,或srv级别的配置项参数;
* 这与当前的ngx_rtmp_conf_ctx_t是在解析rtmp{}或server{}块时创建有关;
*/
void **srv_conf;
/*
* 数组中的每个成员都是由所有HTTP模块create_loc_conf方法创建的与location有关的配置项结构体,
* 它们存放着main级别、srv级别、loc级别的配置项参数;
* 这与当前的ngx_rtmp_conf_ctx_t是在解析rtmp{}、server{}或app{}块时创建有关;
*/
void **app_conf;
} ngx_rtmp_conf_ctx_t;
ngx_rtmp_module是rtmp模块的核心模块,该模块的功能是:
- 1)定义新的 RTMP 模块类型,
- 2)每个 RTMP 模块定义通用接口ngx_rtmp_module_t 结构体,管理RTMP 模块生成的配置项结构体,并解析RTMP类配置项
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, /* worker进程启动的初始化操作,主要初始化ngx_rtmp_init_queue队列 */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
ngx_rtmp_module作为核心模块之一,必须要定义核心模块通用的上下文结构,该结构体只定义模块的名称
static ngx_core_module_t ngx_rtmp_module_ctx = {
ngx_string("rtmp"),
NULL,
NULL
};
ngx_rtmp_module模块还定义了ngx_rtmp_commands的配置项数组,rtmp{}块的rtmp配置项数组。
该命令数组只定义了一个配置项rtmp,主要用来rtmp模块的初始化作用。
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
};
ngx_rtmp_module 核心模块的初始化
rtmp模块的初始化过程主要在ngx_rtmp_block函数当中完成的,先分析整个函数的初始化rtmp的过程,再整体梳理一下流程。
static char *
ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
ngx_uint_t i, m, mi, s;
ngx_conf_t pcf;
ngx_array_t ports;
ngx_module_t **modules;
ngx_rtmp_listen_t *listen;
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;
/* 分配HTTP框架的上下文结构ngx_rtmp_conf_ctx_t空间 */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
/*
* conf 是结构体ngx_cycle_t 成员conf_ctx数组中的元素,
* 该元素conf指向ngx_rtmp_module模块所对应的配置项结构信息;
*/
*(ngx_rtmp_conf_ctx_t **) conf = ctx;
/* count the number of the rtmp modules and set up their indices */
#if (nginx_version >= 1009011)
ngx_rtmp_max_module = ngx_count_modules(cf->cycle, NGX_RTMP_MODULE);
#else
ngx_rtmp_max_module = 0;
/* 初始化所有RTMP模块的ctx_index序号 */
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++;
}
#endif
/* the rtmp main_conf context, it is the same in the all rtmp contexts */
/*
* 分配存储RTMP模块main级别下的main_conf配置项的空间;
*/
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
* 分配存储RTMP模块main级别下的srv_conf配置项的空间;
*/
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
* 分配存储RTMP模块main级别下的app_conf配置项的空间;
*/
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模块,为每个RTMP模块创建main级别的配置项结构main_conf、srv_conf、app_conf;
*/
for (m = 0; modules[m]; m++) {
if (modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = modules[m]->ctx;
mi = modules[m]->ctx_index;
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_app_conf) {
ctx->app_conf[mi] = module->create_app_conf(cf);
if (ctx->app_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
}
/*
* 保存待解析配置项结构cf的副本为pcf,待解析完毕后恢复cf;
* 这里备份是由于配置指令解析函数ngx_conf_parse递归调用,因此为了不影响外层的调用环境;
*/
pcf = *cf;
/*
* 把RTMP模块解析指令的上下文参数保存到配置项结构ngx_rtmp_conf_ctx_t ctx中;
*/
cf->ctx = ctx;
/* 遍历所有RTMP模块,调用每个模块的preconfiguration回调函数 */
for (m = 0; modules[m]; m++) {
if (modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = modules[m]->ctx;
if (module->preconfiguration) {
if (module->preconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
/* parse inside the rtmp{} block 调用模块通用配置项解析函数ngx_conf_parse解析rtmp{}块内的指令*/
cf->module_type = NGX_RTMP_MODULE; /* 模块类型为RTMP模块 */
cf->cmd_type = NGX_RTMP_MAIN_CONF; /* 指令类型为RTMP模块的main级别指令 */
/*
* 开始解析http{}块内的指令;这里必须注意的是:http{}块内可能会包含server{}块,
* 而server{}可能会包含app{}块,还需注意的是http{}块内可能有多个server{}块,
* app{}块也可能有多个,还有一些子块,类似于rec这种
* 因此,配置项解析函数ngx_conf_parse是被递归调用的;
*/
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{}块的配置解析了包括里面嵌套的srv和app
* /
/* 获取ngx_rtmp_core_module模块的main_conf配置项结构 */
cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];
/* 获取所有srv_conf配置项结构 */
cscfp = cmcf->servers.elts;
/*
* 遍历所有RTMP模块,并初始化每个RTMP模块的main_conf结构,
* 同时合并srv_conf 结构(当然srv_conf结构里面包含app_conf结构,所有也合并app_conf结构);
*/
for (m = 0; modules[m]; m++) {
if (modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
/*
* ngx_modules[m]是一个ngx_module_t模块结构体,
* 它的ctx成员相对于RTMP模块来说是ngx_rtmp_module_t接口;
*/
module = modules[m]->ctx;
/* 获取当前RTMP模块在RTMP模块类的序号 */
mi = modules[m]->ctx_index;
/* init rtmp{} main_conf's */
cf->ctx = ctx;
/* 初始化RTMP模块的main_conf结构 */
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;
/* 合并当前main和srv中的配置,如果main和srv都配置了同一指令,采用srv里面的,没有则用main里面的 */
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;
}
}
/* 合并当前main,srv,app中的配置,如果main,srv,app都配置了同一指令,采用app里面的,app > srv > main */
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;
}
}
}
}
/* 初始化可添加自定义处理方法的7个HTTP阶段的动态数组 */
if (ngx_rtmp_init_events(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
/* 给各个NGX-RTMP-MODULE类型的rtmp模块注册各种消息handler回调,可以去各个模块的 postconfiguration函数当中看注册的rtmp消息回调handler */
for (m = 0; modules[m]; m++) {
if (modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = modules[m]->ctx;
if (module->postconfiguration) {
if (module->postconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
*cf = pcf;
/* 初始化rtmp模块事件handler数组 */
if (ngx_rtmp_init_event_handlers(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
/* 给监听端口数组进行初始化 */
if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_rtmp_conf_port_t))
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
listen = cmcf->listen.elts;
/* 将配置当中监听的端口放到端口数组当中 */
for (i = 0; i < cmcf->listen.nelts; i++) {
if (ngx_rtmp_add_ports(cf, &ports, &listen[i]) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
/* 设置server与监听端口的关系,给每个端口和地址进行关联起来,创建监听相关信息 */
return ngx_rtmp_optimize_servers(cf, &ports);
}
各个 RTMP 模块都选择在 postconfiguration 回调函数中将自定义方法添加到 handler 动态数组中,原因是在所有 postconfiguration 回调函数调用前,nginx 执行了 ngx_rtmp_init_event 初始化 event 结构,然后在调用所有的 postconfiguration 回调函数以后执行了 ngx_rtmp_init_event_handlers 为 cmcf->events事件handler数组赋值。
通过上面函数的分析,大致可以总结rtmp模块初始化整体流程:
- 在配置文件中遇到 rtmp{} 块配置,则 RTMP 框架开始初始化并启动,其由函数 ngx_rtmp_block() 实现;
- rtm框架初始化所有 RTMP 模块的序列号,并创建 3 个类型为 ngx_http_conf_ctx_t 结构的数组用于存储所有HTTP 模块的create_main_conf、create_srv_conf、create_loc_conf方法返回的指针地址;
- 调用每个 RTMP 模块的 preconfiguration 方法;
- RTMP框架调用函数 ngx_conf_parse() 开始循环解析配置文件 nginx.conf 中的rtmp{}块里面的所有配置项,rtmp{} 块内可嵌套多个server{} 块,而 server{} 块可嵌套多个 application{},因此配置项解析函数是递归调用;
- RTMP 框架处理完毕 rtmp{} 配置项,根据解析配置项的结果,必要时调用ngx_rtmp_merge_servers 方法进行配置项合并处理,即合并main、srv、app 级别下server、application 相关的配置项;
- 初始化可添加处理方法的 RTMP 不同rtmp消息命令的动态数组;
- 调用所有 RTMP 模块的 postconfiguration 方法使RTMP模块可以注册不同的rtmp消息handler,使得不同模块可以处理不同消息
- 根据 RTMP 模块处理 RTMP 不同消息阶段的方法构造 cmcf->events 数组;
- 构造 server 相关的监听端口,并设置新连接事件的回调方法为ngx_rtmp_init_connection ;
- 继续处理其他 rtmp{} 块之外的配置项,直到配置文件解析器处理完所有配置项后通知Nginx 主循环配置项解析完毕。此时,Nginx 才会启动Web 服务器;