nginx-rtmp模块初始化

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指针的方法回调即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

brid.huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值