#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_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/