百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Nginx模块开发:从源码剖析整个Nginx框架

bigegpt 2024-10-12 06:46 3 浏览

视频解析:

浅析nginx模块用在哪里?手把手带你实现nginx模块开发

16万行nginx源码,就该这么读

ngx_module_t结构体

模块又分为ngx_core_module_t和ngx_xxx_module_t,而在ngx_module_t中会包含这两个结构,只不过不同类的模块包含不同的结构,一般会 用ctx表示,ngx_module_t又用type这个域用来表示模块的类型

struct ngx_module_s {
   /*ctx_index表示当前模块在这类模块中的序号。这个 成员常常是由管理模块的 一个Nginx核心模块设置的,对于所有的http模块而言,ctx_index是由核心模块ngx_http_module设置的,ctx_index非常重要,Nginx的模块化设计非常依赖各个模块的顺序*/
    ngx_uint_t            ctx_index;
    /*index表示当前模块在ngx_modules数组中 的序号。*/
    ngx_uint_t            index; 

    char                 *name;
    // sqare系列的 保留变量,暂未使用
    ngx_uint_t            spare0;
    ngx_uint_t            spare1;
    // 模块的版本
    ngx_uint_t            version;
    const char           *signature;
   /**ctx用于指向一类的模块的上下文结构体,Nginx模块有许多种类,不同模块之间的功能差别很大。例如事件模块主要处理I/O事件相关的功能,每个模块都有自己的特性,ctx将会指向特定类型模块的公共接口,例如在Http 模块 中,ctx指向ngx_http_module_t结构体*/
    void                 *ctx;
    // commands将处理nginx.conf中 的配置项
    ngx_command_t        *commands;
    /*type 表示该模块的类型,它与ctx指针是紧密相关的,NGX_HTTP_MODULE、NGX_CORE_MODULE、NGX_CONF_MODULE、NGX_EVENT_MODULE、NGX_EVENT_MODULE、NGX_STREAM_MODULE*/
    ngx_uint_t            type;
    /*在Nginx 启动、停止过程中,以下7个函数指针表示有7个执行点分别调用这个7个方法,对于任一方法,如果不需要Nginx在某个时刻执行 它,那么 简单地把它设为NULL空指针 即可*/
    /* 虽然字面上理解应当在master进程启动时调用,但到目前为止,框架代码从来不会调用它,因此 要设为NULL*/
    ngx_int_t           (*init_master)(ngx_log_t *log);
    /*init module 回调方法在初始化所有模块被调用,在master/worker模式下,这个阶段将在worker进程前完成 */
    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
   /*init_process回调方法在正常服务前被调用,在master/worker模式下,多个worker子进程已经产生,在每个worker进程初始化会调用所有模块的init_process函数*/
    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    // Nginx目前还支持多线程,所以这个没有被调用过
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
   /* exit_process回调方法在服务停止前调用。在master/worker模式下,worker会在退出前调用它*/
    void                (*exit_process)(ngx_cycle_t *cycle);
    //该方法 会在master进程退出前调用它
    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;
};

这里看到有两个index,分别是ctx_index和index,他们的区别是这样子的(用http模块举例),ctx_index保存了每一个http module的config的索引,而所有的http module config是分别保存在nginx_conf_t的ctx数组中的.而index保存了每一个core module的config的索引,而每个core module的config都是保存在cycle的conf_ctx中的,下面的代码轻易看出

#define ngx_http_conf_get_module_main_conf(cf, module)                        
    ((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]

#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]1234

ngx_core_module_t都包括(log, event, event_openssl, http, mail,google perftools),可以看到http module本身也是一个core module。这里要注意还有一个conf module,只不过它也是用core module这个数据结构。

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;
123456

ngx_http_module_t包括所有src/http/下面的模块,它就包含了所有的http module,它们都从属于http core模块。

typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

    void       *(*create_main_conf)(ngx_conf_t *cf);
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

还有一个ngx_module_s还有一个很重要的成员就是ngx_command_t。这个域包含当前 模块 所有包含的指令


解析完上面的数据结构,下面将从整体剖析整个Nginx框架的走向,前面的一篇博客Nginx模块自主开发六:源码剖析配置文件解析过程详细介绍了ngx_init_cycle的流程,这篇博客将主要从ngx_init_cycle如何串通所有模块 的流程说起 。(有些部分会重复,但是为了理清整个Nginx框架的流程还是值得)

需要C/C++ Linux服务器架构师学习资料后台私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

Nginx在ngx_init_cycle中通过下列代码创建所有的 configure,它 通过调用每个core module 的Create_conf的方法来创建对应的conf,每个对象都保存在全局的conf__ctx中。

/*
创建这些数据结构的工作都需要在这一步进行。Nginx框架只关心NGX_CORE_MODULE核
心模块,这也是为了降低框架的复杂度。这里将会调用所有核心模块的create conf方法(也只有核心模块才有这个方法),这意味着需要所有的核心
模块开始构造用于存储配置项的结构体。其他非核心模块怎么办呢?其实很简单。这些模块大都从属于一个核心模块,如每个HTTP模块都由ngx_http_module
管理(如图8-2所示),这样ngx_http_module在解析自己感兴趣的“http”配置项时,将会调用所有HTTP模块约定的方法来创建存储配置项的结构体
(xxx_create_main_conf、xxx_create_srv_conf、xxx_create_loc_conf方法)。
*/
    for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->type != NGX_CORE_MODULE) {
            continue;
        }
    //得到core module
        module = cycle->modules[i]->ctx;
     /如果create_conf存在,则直接创建config
        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;  
        }
    }
       .......
    conf.ctx = cycle->conf_ctx;  //这样下面的ngx_conf_param解析配置的时候,里面对conf.ctx赋值操作,实际上就是对cycle->conf_ctx[i]
    conf.cycle = cycle;
    conf.pool = pool;
    conf.log = log;
    conf.module_type = NGX_CORE_MODULE;
    conf.cmd_type = NGX_MAIN_CONF;

当所有的core module的config都创建完毕后,就要开始解析配置文件了,解析配置文件它会一行行读取,然后如果遇到指令,则会查找到对应的ngx_command_t对象,然后执行对应的回调set方法。这里所有动作都在ngx_conf_parse这个函数中进行。然后在ngx_conf_parse会判断cf是否有handler回调,如果有的话,优先调用handler回调,如果没有,则会进入ngx_conf_handler进行一般处理。
一般来说,如果没有自定义handler ,解析文件的过程
ngx_init_cycle()->ngx_conf_parse()->ngx_conf_handler()解析 ,最后通过cmd->set方法进行回调。这时就进入我们ngx_command_t设置的 回调函数 。比如,ngx_http.c中 的ngx_http_block函数。

// 文件中的ngx_http.c的ngx_http_bock函数
// 这个函数在分配 ctx的空间之后,同时给main,serv和conf分配空间之后,初始化每个空间的配置
for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = cf->cycle->modules[m]->ctx; // 注意module的赋值情况是ngx_module_t的成员ctx
        mi = cf->cycle->modules[m]->ctx_index; //得到对应的索引
     //如果有对应的回调,则调用回调函数,然后将返回的模块config设置到ctx的对应的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;
            }
        }

        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_loc_conf) { //
            ctx->loc_conf[mi] = module->create_loc_conf(cf);
            if (ctx->loc_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }
    ....
    // 递归解析
    rv = ngx_conf_parse(cf, NULL);

http module相关的config是在ngx_http_block中创建的,在ngx_http_block中会创建,初始化,合并config,以及整个http handler phase的初始化等等.
先是初始化所有的http module的ctx_index.
当http block完全parse完毕之后,就需要merge(main和srv或者srv和loc)相关的config了。不过在每次merge之前都会首先初始化main conf。

  cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
    cscfp = cmcf->servers.elts;

    for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = cf->cycle->modules[m]->ctx;
        mi = cf->cycle->modules[m]->ctx_index;

        /* init http{} main_conf's */

        if (module->init_main_conf) {
            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
     //然后开始merge config。
     /* 每个级别都会保存对应的ctx(main/ser/loc),怎么说呢,就是在解析HTTP main中创建了3个ctx(main/srv/loc),而在HTTP srv block中将会创建2个ctx(srv/loc),这时发生重复了,那就需要merge了。比如一个命令(srv_offset)在HTTP main中有一个,那么Nginx将会把它放入到HTTP main的ctx的srv ctx中,然后server block也有一个,那么Nginx会继续把它放到Server ctx的 srv_conf中,最后merge他们。*/
        rv = ngx_http_merge_servers(cf, cmcf, module, mi);//合并server{}及其以下的local{}
        if (rv != NGX_CONF_OK) {
            goto failed;
        }

当merge完毕之后,然后就是location的处理,初始化location tree,创建handler phase,调用postconfiguration,以及变量的初始化。

    /*
    经过配置的读取之后,所有server都被保存在http core模块的main配置中的servers数组中,而每个server里面的location都被按配置中
    出现的顺序保存在http core模块的loc配置的locations队列中,上面的代码中先对每个server的location进行排序和分类处理,这一步
    发生在 ngx_http_init_location()函数中:
    */
      /*location处理比较复杂*/
       if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
/*    根据已经按照location字符串排序过的双向链表,快速地构建静态的二叉查找树。与ngx_http_init_locations方法类似,速个操作也是递归进行的
          */
        /*
        下面的ngx_http_init_static_location_trees函数就会将那些普通的location(就是ngx_http_init_locations中name noname regex以外的location(exact/inclusive)),
        即staticlocation,进行树化(一种三叉树)处理,之所以要做这样的处理,是为了在处理http请求时能高效的搜索的匹配的location配置。
        */

        if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }

  //初始化handler phase
    if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
   for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;
//调用回调
        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }
//开始初始化变量
    if (ngx_http_variables_init_vars(cf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

做完上面的工作之后,下面进行初始化socket的相关东西,比如设置读写回调 函数等


    if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }


    /* optimize the lists of ports, addresses and server names */

    if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

ngx_cycle_init剩下的部分,当配置文件解析完毕后,就开始初始化core module的config

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;
            }
        }
    }

接下来就是创建并初始化所有创建的共享内存

  if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {
            goto failed;
        }

        if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {
            goto failed;
        }

        if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
            goto failed;
        }

接下来就是开启监听,初始化所有的模块

.....
if (ngx_open_listening_sockets(cycle) != NGX_OK) {
    goto failed;
}
....
  // 初始化所有的模块
    if (ngx_init_modules(cycle) != NGX_OK) {
        /* fatal */
        exit(1);
    }

到时为止ngx_init_cycle()函数主要的工作都已经完成了。

总结

要搞清Nginx模块和框架之间的去协作不仅要清楚ngx_command_t结构体、ngx_module_t结构体,ngx_conf_t结构以及ngx_cycle_t结构体的关系,还要学会使用回调方法来分析整个流程 。上面见到ngx_http_block函数中的location的初始化这里 并没有仔细展开,因为mail 和stream中并没有这一步,等以后有涉及到再 详细了解。

相关推荐

Linux gron 命令使用详解(linux gminer)

简介gron是一个独特的命令行工具,用于将JSON数据转换为离散的、易于grep处理的赋值语句格式。它的名字来源于"grepableon"或"grepable...

【Linux】——从0到1的学习,让你熟练掌握,带你玩转Linu

学习Linux并掌握Java环境配置及SpringBoot项目部署是一个系统化的过程,以下是从零开始的详细指南,帮助你逐步掌握这些技能。一、Linux基础入门1.安装Linux系统选择发行版:推荐...

Linux常用的shell命令汇总(linux中shell的作用)

本文介绍Linux系统下常用的系统级命令,包括软硬件查看、修改命令,有CPU、内存、硬盘、网络、系统管理等命令。说明命令是在Centos6.464位的虚拟机系统进行测试的。本文介绍的命令都会在此C...

零成本搭建个人加密文件保险柜(适用于 Win11 和 Linux)

不依赖收费软件操作简单,小白也能跟着做支持双系统,跨平台使用实现数据加密、防删除、防泄露内容通俗无技术门槛,秒懂秒用使用工具简介我们将使用两个核心工具:工具名用途系统支持Veracrypt创建加密虚...

如何在 Linux 中使用 Gzip 命令?(linux怎么用gzip命令)

gzip(GNUzip)是Linux系统中一个开源的压缩工具,用于压缩和解压缩文件。它基于DEFLATE算法,广泛应用于文件压缩、备份和数据传输。gzip生成的文件通常带有.gz后缀,压缩效率...

Linux 必备的20个核心知识点(linux内核知识点)

学习和使用Linux所必备的20个核心知识点。这些知识点涵盖了从基础操作到系统管理和网络概念,是构建扎实Linux技能的基础。Linux必备的20个知识点1.Linux文件系统层级标...

谷歌 ChromeOS 已支持 7z、iso、tar 文件格式

IT之家6月21日消息,谷歌ChromeOS在管理文件方面进行了改进,新增了对7z、iso和tar等格式的支持。从5月的ChromeOS101更新开始,ChromeOS...

如何在 Linux 中提取 Tar Bz2 文件?

在深入解压方法之前,我们先来了解.tar.bz2文件的本质。.tar.bz2是一种组合文件格式,包含两个步骤:Tar(TapeArchive):tar是一种归档工具,用于将多个文件或目录打包...

如何在 CentOS 7/8 上安装 Kitematic Docker 管理器

Kitematic是一款流行的Docker图形界面管理平台,适用于Ubuntu、macOS和Windows操作系统。然而,其他发行版(如CentOS、OpenSUSE、Fedora、R...

Nacos3.0重磅来袭!全面拥抱AI,单机及集群模式安装详细教程!

之前和大家分享过JDK17的多版本管理及详细安装过程,然后在项目升级完jdk17后又发现之前的注册和配置中心nacos又用不了,原因是之前的nacos1.3版本的,版本太老了,已经无法适配当前新的JD...

爬虫搞崩网站后,程序员自制“Zip炸弹”反击,6刀服务器成功扛住4.6万请求

在这个爬虫横行的时代,越来越多开发者深受其害:有人怒斥OpenAI的爬虫疯狂“偷”数据,7人团队十年心血的网站一夜崩溃;也有人被爬虫逼到极限,最后只好封掉整个巴西的访问才勉强止血。但本文作者却走...

Ubuntu 操作系统常用命令详解(ubuntu必学的60个命令)

UbuntuLinux是一款流行的开源操作系统,广泛应用于服务器、开发、学习等场景。命令行是Ubuntu的灵魂,也是高效、稳定管理系统的利器。本文按照各大常用领域,详细总结Ubuntu必学...

Linux面板8.0.54 测试版-已上线(linux主机面板)

Linux面板8.0.54测试版【增加】[网站]Java项目新增刷新列表按钮【增加】[网站]PHP项目-Apache-服务新增守护进程功能【增加】[网站]Python项目创建/删除网站时新增同时创建...

开源三剑客——构建私有云世界的基石

公共云原生的浪潮正在席卷这个世界,亚马逊AWS、谷歌GCP和微软的Azure年收入增长超过了30%,越来越多的公司和个人开始将自己的服务部署到云环境中,大型数据中心的规模经济带来了成本的降低,可以在保...

2.2k star,一款业界领先的私有云+在线文档管理系统

简介kodbox可道云(原KodExplorer)是业内领先的企业私有云和在线文档管理系统,为个人网站、企业私有云部署、网络存储、在线文档管理、在线办公等提供安全可控,简便易用、可高度定制的私有云产品...