nginx core模块加载其他模块的接口
了解过nginx源码的同学都知道,nginx是高度模块化的一个开源服务,nginx core模块提供了一个固定模式的接口,其他的模块只需要按照core模块提供的接口,做相应的实现,就能够被nginx core模块加载,也就能够成为nginx的一个工作的模块;
nginx core提供的接口为ngx_command_s 结构体和ngx_module_s结构体,我们开发的其他模块,只要实现这两个接口提中的接口,就能够被nginx加载起来;接下来我们分别介绍一些这两个结构体:
我们首先看一下ngx_command_s 结构体,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; //读取配置文件时可能使用的指令 };
该结构体定义的是加载配置的方法,在该结构体中,共有6个字段,上面的代码中已经给出了每个字段的涵义;这个类型的实体会在解析配置文件的时候被调用。
接下来,我们在看一下另一个结构体ngx_module_s,他是模块类型的结构体,定义的是一个模块需要实现的接口,nginx core模块在初始化的时候,会依次调用这个结构体中的函数指针,来初始化新的模块struct ngx_module_s { ngx_uint_t ctx_index; // 所属分类标识.Nginx的模块分为4种,分别是core,http,event和mail,每个模块在使用的技术各不尽相同 ngx_uint_t index; //模块计数器,Nginx为了方便管理模块,定义了一个存放所有模块的数组ngx_modules[] 使用计数器变量ngx_max_module /* 模块的名字,标识字符串,默认是空指针 由脚本生成ngx_module_names数组,然后在ngx_preinit_modules里填充 动态模块在ngx_load_module里设置名字 */ char *name; ngx_uint_t spare0; ngx_uint_t spare1; ngx_uint_t version; //nignx版本 const char *signature; // 模块的二进制兼容性签名,即NGX_MODULE_SIGNATURE void *ctx; // 模块不同含义不同,通常是函数指针表,是在配置解析的某个阶段调用的函数 ngx_core_module_t ngx_command_t *commands; // 模块支持的指令,数组形式,最后用空对象表示结束 ngx_uint_t type; // 模块的类型标识,相当于RTTI,如CORE/HTTP/STRM/MAIL等 ngx_int_t (*init_master)(ngx_log_t *log); //(暂时不调用) ngx_int_t (*init_module)(ngx_cycle_t *cycle); // 在ngx_init_cycle里被调用 ngx_int_t (*init_process)(ngx_cycle_t *cycle); // 在ngx_single_process_cycle/ngx_worker_process_init里调用 ngx_int_t (*init_thread)(ngx_cycle_t *cycle); //初始化线程(暂时不调用) void (*exit_thread)(ngx_cycle_t *cycle); //推出线程(暂时不调用) void (*exit_process)(ngx_cycle_t *cycle); // // 进程退出在ngx_worker_process_exit调用 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; };
同样该结构体中的成员在变量名的后面给出了解释,在这就不做过多的说明,需要注意的是,在这个结构体中我们可以看到ngx_command_t类型的指针,说明ngx_command_t的调用也是通过ngx_module_s结构体。
nginx rtmp模块中接口定义
nginx rtmp模块,正是遵循并且实现了nginx core模块加载三方模块的原则,实现了上面两个接口才得以实现的;接下来我们来看一下在rtmp模块中,是如何定义这两个结构体的;
首先我们看一下ngx_command_t结构体的类型是如何定义的:
在rtmp模块中,我们可以看到定义了一个ngx_command_t结构体的数组,数组的最后一个成员为null成员,这是nginx core模块要求的,因为我们的一个模块可能是由很多的配置项,一个ngx_command_t类型的成员只能解析一个配置项,所以在实际定义中,ngx_command_t 会被定义成成员,最后一个定义成null,是用于在解析配置的时候,提示已经结束了,所有定义的配置项;
在这个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_module_t 的定义,在上面介绍该结构体的时候,已经说过,该结构体会在nginx core模块初始话的时候调用,用来初始化三方模块;在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, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING };
ngx_rtmp_module_ctx成员指向的是rtmp模块的上下文;
ngx_rtmp_commands在刚才已经提到过了,他是在解析rtmp{}配置的时候会调用
NGX_CORE_MODULE用以说明,他是rtmp的核心模块
ngx_rtmp_init_process是在创建子进程的时候,调用的;
上面介绍了rtmp模块的定义,至于在core模块中,合适会进行调用,请大家去查阅nginx模块的代码,在后续时间宽裕的情况下,我这边也会在做介绍,这块暂时不做介绍;
nginx rtmp模块加载
在nginx rtmp模块中,初始化该模块,首先调用的方法为ngx_rtmp_commands[0]变量中的ngx_rtmp_block方法,让我们一起看一下该方法:
根据上面nginx rtmp模块中接口定义中介绍的,在配置文件解析的时候,当解析到“rtmp”配置项的时,会调用ngx_rtmp_block方法对配置文件中的rtmp{}配置模块进行解析;这个时候,开始了rtmp模块真正的初始化。
在了解如何解析rtmp的配置文件之前,我们先了解一些rtmp的配置文件的样式;例如rtmp的配置文件:rtmp{ server{ listen 80; application app1{ live on; sync 1; } application app2{ live on; sync 1 } } }
我们可以看出,在rtmp模块中,存在server模块,它可以存在多个,在每个server模块中又存在多个application模块,由于他们都属于rtmp模块,所以会在ngx_rtmp_block模块中解析并存储在内存中;
从上面的rtmp的配置项中可以看出来,在rtmp中存在server和application的概念:
1、server就相当于服务进程,nginx支持监听多个端口,每一个端口就可以是一个独立的服务进程;
2、application相当于服务中提供的每一个功能,一个服务可以提供多个功能,所以可以配置多个application;
通过前面对rtmp模块的介绍,我们知道,rtmp模块中存在多个子模块,并且子模块的初始化也会随着rtmp模块的初始化而随一起进行;接下来我们将详细分析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; //RTMP模块,配置文件对象指针 ngx_rtmp_core_srv_conf_t *cscf, **cscfp; ngx_rtmp_core_main_conf_t *cmcf; 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 */ #if (nginx_version >= 1009011) ngx_rtmp_max_module = ngx_count_modules(cf->cycle, NGX_RTMP_MODULE); #else ngx_rtmp_max_module = 0; for (m = 0; ngx_modules[m]; m++) { <font color='red'>//遍历nginx的所有模块,为属于rtmp模块的各个子模块分配内部的索引号,ngx_modules这个数组是个全局数组,在编译的时候,会存放所有模块的指针</font> 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 */ //为main配置项分配空间,这个空间中存储的配置信息,是整个rtmp共用的,也就是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 */ //为rtmp中的每个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 */ //为每个server中的application域配置项分配空间 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 */ #if (nginx_version >= 1009011) modules = cf->cycle->modules; #else modules = ngx_modules; #endif //遍历配置模块数组,为每个rtmp模块创建创建对应的配置项上下文 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; } } } pcf = *cf; 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 */ cf->module_type = NGX_RTMP_MODULE; cf->cmd_type = NGX_RTMP_MAIN_CONF; rv = ngx_conf_parse(cf, NULL); //解析rtmp{}域中的配置项 if (rv != NGX_CONF_OK) { *cf = pcf; return rv; } /* init rtmp{} main_conf's, merge the server{}s' srv_conf's */ cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index]; cscfp = cmcf->servers.elts; for (m = 0; modules[m]; m++) { if (modules[m]->type != NGX_RTMP_MODULE) { continue; } module = modules[m]->ctx; mi = modules[m]->ctx_index; /* init rtmp{} main_conf's */ cf->ctx = ctx; //对rtmp{}域配置进行初始化 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; 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; } //将server和rtmp域的配置信息拷贝到application的配置信息空间中取,这是由于rtmp域的配置信息,是整个rtmp模块共享的,server域的配置信息是这个server域下的所有的application共享的,按照这种方式,application需要包含整个配置项中除其他application之外所有的其他配置信息 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; } } } } //初始化各个类型的事件,[**<font color='red'>在rtmp协议中</font>**](https://blog.csdn.net/weixin_42146646/article/details/114726501),messageTypeID字段取值的不同,表示不同的涵义,每个messageTypeID都对应一个可执行的事件,这个地方仅仅是为每个事件分配存储空间 if (ngx_rtmp_init_events(cf, cmcf) != NGX_OK) { return NGX_CONF_ERROR; } for (m = 0; modules[m]; m++) { if (modules[m]->type != NGX_RTMP_MODULE) { continue; } module = modules[m]->ctx; //这个在解析完配置文件之后,进行处理的方法回调(<font color='red'>后面会有介绍</font>) if (module->postconfiguration) { if (module->postconfiguration(cf) != NGX_OK) { return NGX_CONF_ERROR; } } } *cf = pcf; //为RTMP协议中的每个MessageTypeID注册一个可执行的事件方法 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; //将server{}域中的listen字段的值,存储到port数组中 for (i = 0; i < cmcf->listen.nelts; i++) { if (ngx_rtmp_add_ports(cf, &ports, &listen[i]) != NGX_OK) { return NGX_CONF_ERROR; } } //进行服务的初始化,也就是创建监听等 return ngx_rtmp_optimize_servers(cf, &ports); }
上面这个是ngx_rtmp_block方法的整体一流程,接下来我们将看一下,对于messageTypeID注册了那些事件,以及如何进行服务的初始化;
messageTypeID事件的注册函数为ngx_rtmp_init_event_handlers,我们看一下在这个方法中做了些什么:
static ngx_int_t ngx_rtmp_init_event_handlers(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf)
{
ngx_hash_init_t calls_hash;
ngx_rtmp_handler_pt *eh;
ngx_rtmp_amf_handler_t *h;
ngx_hash_key_t *ha;
size_t n, m;
//这个是针对于MessageTypeID为1、2、3、5、6注册的事件的定义
static size_t pm_events[] = {
NGX_RTMP_MSG_CHUNK_SIZE,
NGX_RTMP_MSG_ABORT,
NGX_RTMP_MSG_ACK,
NGX_RTMP_MSG_ACK_SIZE,
NGX_RTMP_MSG_BANDWIDTH
};
//这个是针对于MessageTypeID为15、16、17、18、19、20的时候,数据为amf而注册的事件的定义
static size_t amf_events[] = {
NGX_RTMP_MSG_AMF_CMD,
NGX_RTMP_MSG_AMF_META,
NGX_RTMP_MSG_AMF_SHARED,
NGX_RTMP_MSG_AMF3_CMD,
NGX_RTMP_MSG_AMF3_META,
NGX_RTMP_MSG_AMF3_SHARED
};
/* init standard protocol events */
//这个是针对于MessageTypeID为1、2、3、5、6注册的事件
for(n = 0; n < sizeof(pm_events) / sizeof(pm_events[0]); ++n)
{
eh = ngx_array_push(&cmcf->events[pm_events[n]]);
*eh = ngx_rtmp_protocol_message_handler;
}
/* init amf events */
//这个是针对于MessageTypeID为15、16、17、18、19、20的时候,数据为amf而注册的事件
for(n = 0; n < sizeof(amf_events) / sizeof(amf_events[0]); ++n)
{
eh = ngx_array_push(&cmcf->events[amf_events[n]]);
*eh = ngx_rtmp_amf_message_handler;
}
/* init user protocol events */
//当messageTypeID为4的时候注册的事件
eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_USER]);
*eh = ngx_rtmp_user_message_handler;
/* aggregate to audio/video map */
//MessageTypeID为22的时候,注册的事件
eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AGGREGATE]);
*eh = ngx_rtmp_aggregate_message_handler;
//针对于amf中,携带的命令的处理方法,注册的事件,是从各个application的配置文件中获取过来的,存储使用hash方式
/* init amf callbacks */
ngx_array_init(&cmcf->amf_arrays, cf->pool, 1, sizeof(ngx_hash_key_t));
h = cmcf->amf.elts;
for(n = 0; n < cmcf->amf.nelts; ++n, ++h)
{
ha = cmcf->amf_arrays.elts;
for(m = 0; m < cmcf->amf_arrays.nelts; ++m, ++ha)
{
if (h->name.len == ha->key.len && !ngx_strncmp(h->name.data, ha->key.data, ha->key.len))
{
break;
}
}
if (m == cmcf->amf_arrays.nelts)
{
ha = ngx_array_push(&cmcf->amf_arrays);
ha->key = h->name;
ha->key_hash = ngx_hash_key_lc(ha->key.data, ha->key.len);
ha->value = ngx_array_create(cf->pool, 1, sizeof(ngx_rtmp_handler_pt));
if (ha->value == NULL) {
return NGX_ERROR;
}
}
eh = ngx_array_push((ngx_array_t*)ha->value);
*eh = h->handler;
}
calls_hash.hash = &cmcf->amf_hash;
calls_hash.key = ngx_hash_key_lc;
calls_hash.max_size = 512;
calls_hash.bucket_size = ngx_cacheline_size;
calls_hash.name = "amf_hash";
calls_hash.pool = cf->pool;
calls_hash.temp_pool = NULL;
if (ngx_hash_init(&calls_hash, cmcf->amf_arrays.elts, cmcf->amf_arrays.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}
上面这些是RTMP模块中各个命令的回调方式的注册;接下来,我们将看一下服务程序如何启动,也就是ngx_rtmp_optimize_servers方法:
static char *
ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
{
ngx_uint_t i, p, last, bind_wildcard;
ngx_listening_t *ls;
ngx_rtmp_port_t *mport;
ngx_rtmp_conf_port_t *port;
ngx_rtmp_conf_addr_t *addr;
port = ports->elts;
for (p = 0; p < ports->nelts; p++)
{
ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts, sizeof(ngx_rtmp_conf_addr_t), ngx_rtmp_cmp_conf_addrs);
addr = port[p].addrs.elts;
last = port[p].addrs.nelts;
/*
* if there is the binding to the "*:port" then we need to bind()
* to the "*:port" only and ignore the other bindings
*/
if (addr[last - 1].wildcard) {
addr[last - 1].bind = 1;
bind_wildcard = 1;
} else {
bind_wildcard = 0;
}
i = 0;
while (i < last) {
if (bind_wildcard && !addr[i].bind) {
i++;
continue;
}
ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);
if (ls == NULL) {
return NGX_CONF_ERROR;
}
ls->addr_ntop = 1;
ls->handler = ngx_rtmp_init_connection;
ls->pool_size = 4096;
/* TODO: error_log directive */
ls->logp = &cf->cycle->new_log;
ls->log.data = &ls->addr_text;
ls->log.handler = ngx_accept_log_error;
ls->keepalive = addr[i].so_keepalive;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
ls->keepidle = addr[i].tcp_keepidle;
ls->keepintvl = addr[i].tcp_keepintvl;
ls->keepcnt = addr[i].tcp_keepcnt;
#endif
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
ls->ipv6only = addr[i].ipv6only;
#endif
mport = ngx_palloc(cf->pool, sizeof(ngx_rtmp_port_t));
if (mport == NULL) {
return NGX_CONF_ERROR;
}
ls->servers = mport;
if (i == last - 1) {
mport->naddrs = last;
} else {
mport->naddrs = 1;
i = 0;
}
switch (ls->sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
if (ngx_rtmp_add_addrs6(cf, mport, addr) != NGX_OK) {
return NGX_CONF_ERROR;
}
break;
#endif
default: /* AF_INET */
if (ngx_rtmp_add_addrs(cf, mport, addr) != NGX_OK) {
return NGX_CONF_ERROR;
}
break;
}
addr++;
last--;
}
}
return NGX_CONF_OK;
}
上面的这个方法比较简单,主要就是创建网络监听和保存监听句柄,需要注意的是,在由链接上来之后,他会回调ngx_rtmp_init_connection方法进行处理;
上面是整个rtmp模块的初始化流程;
RTMP模块中的子模块的初始化处理
在前面我们说过rtmp存在很多的application,每一个application就是一个子模块,他们的初始化会随着RTMP模块的初始化而随之进行,那么他们是在什么时候进行的呢?
从前面的介绍可知,我们会在RTMP初始化的三个时期回去调用子模块
1、一个是在解析配置文件之前,通过模块的回调指针module->preconfiguration调用子模块,
2、一个是在解析配置文件的时候,在ngx_conf_handler方法中,当匹配到对应的配置项的时候,会调用子模块定于的ngx_command_t 接口实例来解析配置文件,
3、还有一个是在配置文件解析完成之后调用module->postconfiguration来调用子模块;所以我们只需要去各个子模块中,查看他们对于preconfiguration和postconfiguration指针的方法回调即可