在上一篇中有提到目前ngx共有六种模块,分别是:
NGX_CORE_MODULE
NGX_CONF_MODULE
NGX_HTTP_MODULE
NGX_MAIL_MODULE
NGX_EVENT_MODULE
NGX_STREAM_MODULE
如果按照一种更抽象的方式来划分,除了NGX_CONF_MODULE外,我们基本上可以把ngx模块类型划分为两种,一种是核心类型(NGX_CORE_MODULE),另一种是非核心类型。
核心类型模块主要用来搭建ngx基础功能和为非核心类型模块起支撑作用,比如核心模块src/http/ngx_http.c的主要作用就是支撑(或管理)所有http模块可以正常工作。非核心类型模块主要完成各自的业务功能,比如ngx_http_proxy_module主要完成反向代理工作,而ngx_http_gzip_module模块则完成数据压缩。
不管是核心模块还是非核心模块,他们在整体的架构实现上差别其实并不大,最大的差别基本上有两个方面,一个是指令集的实现(各个模块都有自己的指令集),另一个是上下文的实现(ngx_module_t->ctx字段)。基本上每种模块类型都会有一个特定的上下定义,上下文一般限定了该类型模块在文件解析过程中应该做的事(或能够做的事);指令集则描述了某个具体模块都有哪些功能,比如ngx_http_rewrite_module模块中的return指令可以直接返回数据。
正常情况下,在我们的实际业务场景中,编写一个非核心模块,要比编写一个核心模块的几率大的多得多,不过为了让读者对模块编写有一个更清晰的认识,我们循序渐进,这篇先实现一个核心自定义模块,等下篇再实现一个非核心自定义模块。
1开始之前
上篇有说过,要扩展一个模块,一般需要约定三种数据,一种是ngx_modue_t.ctx字段,另一种是ngx_modue_t.type字段,还有一个是该模块提供的指令集commands[](元素类型为ngx_command_t)。
对于以上三种数据,因为我们明确指出是要实现一个核心模块,所以type的值就是NGX_CORE_MODULE,其它两种则需要根据我们模块要完成的功能而定。
编写这个核心模块的主要目的是让读者熟悉模块的工作机制,所以并不会有任何特殊的业务功能,假设这个模块有如下两个指令:
my_core_simple xxx …;
my_core_block { // anything }
第一个条指令可以接收任意参数,作用是指令解析时打印出指令后的参数;第二个指令的参数是一个参数块,在它里面可以写任意多个参数,比如这样:
my_core_block {
aaa 111 222 333;
bbb 122 33;
}
它的作用是在指令解析时打印出内部参数。
2约定上下文
核心模块在ngx中的上下文定义是这样的:
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;
我们对它的实现如下:
static ngx_core_module_t ngx_my_core_module_ctx = {
ngx_string("my_core"),
ngx_my_core_create_conf,
ngx_my_core_init_conf
};
其中“my_core”表示这个核心模块的名字,随后两个方法则是对*(*create_conf)和*(*ini_conf)方法的实现,他们的具体实现分别如下:
static void*
ngx_my_core_create_conf(ngx_cycle_t *cycle)
{
// 向标准输出打印数据
ngx_log_stderr(0, "start runing ngx_my_core_create_conf()");
// ngx要求该方法不能返回空,否则启动失败
//我们本例不需要这个数据,随便返回一个
return cycle;
}
static char *
ngx_my_core_init_conf(ngx_cycle_t *cycle, void *conf)
{
// 向标准输出打印数据
ngx_log_stderr(0, "start runing ngx_my_core_init_conf()");
return NULL;
}
第一个方法在开始解析配置文件前执行,该方法一般用来创建一个自定义的结构体,你可以用这个结构体存放一些需要在运行时用到的信息(比如某个指令的配置信息);第二个方法在配置文件解析完毕后执行,可以在这里做一些初始化之类的工作。我们这里实现的及其简单,就是打印了各自的方法名。
3约定指令集
在ngx中每条指令都会用一个结构体来表示,这个结构体的具体定义如下:
typedef struct ngx_command_s ngx_command_t;
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) 表示一个函数指针(用来存放函数),当ngx解析到该指令后就会回调这个方法。
-
其它的我们这次用不到,等后续用到再说。
我们的模块共有两条指令,用数组来存放他们,具体如下:
static ngx_command_t ngx_my_core_commands [] = {
{ ngx_string("my_core_simple"),
NGX_MAIN_CONF| NGX_CONF_TAKE1,
ngx_my_core_simple,
0,
0,
NULL },
{ ngx_string("my_core_block"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_my_core_block,
0,
0,
NULL },
ngx_null_command
};
数组总共有三个元素,第一个表示my_core_simple指令的定义。
第二个是my_core_block指令的定义。
最后一个用来表示指令集结尾,是一个宏定义。
NGX_MAIN_CONF表示指令只能出现在主配置这一级别(不能在某个xxx{}块内)。
NGX_CONF_TAKE1表示指令只能带一个参数。
NGX_CONF_BLOCK表示该指令是一个块指令(比如xxx {})。
NGX_CONF_NOARGS表示该指令不能携带参数。
4实现指令的回调函数
每个指令对应的回调函数必须是如下形式:
char * xxx(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
其中入参cf存放了一些当前解析数据的上下文,具体内容我们后续用到的时候再说。
cmd表示当前解析到的指令定义。
conf表示ngx_my_core_create_conf方法(在ngx_my_core_module_ctx中设置的)的返回值。
第一个指令对应的函数实现如下:
static char *
ngx_my_core_simple(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_str_t *name;
// 取出指令名字
name = cf->args->elts;
// 向标准输出打印指令名字和指令值
ngx_log_stderr(0, "find a directive name[%s] value[%s] method[ngx_my_core_simple]", name->data, name[1].data);
return NGX_CONF_OK;
}
其中cf->args->elts表示解析到的指令串值,是一个数组,比如解析到的指令串是:
my_core_simple 111;
那么该数组的第一个值就是“my_core_simple”,第二个则是“111”。
第二个指令因为是一个块指令,实现上稍微复杂一点,具体如下:
static char *
ngx_my_core_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
ngx_conf_t save;
ngx_str_t *name;
// 取出指令名字
name = cf->args->elts;
// 向标准输出打印指令名字和指令值
ngx_log_stderr(0, "find a directive block name[%s] method[ngx_my_core_block]", name->data);
// 开始解析my_core_block{}块内的数据
save = *cf;
cf->handler = ngx_my_core_block_handler;
cf->handler_conf = conf;
// 块内数据由该方法负责解析(ngx自带的方法)
rv = ngx_conf_parse(cf, NULL);
*cf = save;
return rv;
}
ngx在解析配置文件的时候,默认情况下,每当解析出一个“指令串”(ngx内部叫token)时,会把该串的第一个“单词”当成指令,然后用去模块中匹配指令定义,匹配成功后就会回调对应的指令方法。
指令串的定义是这样的,以“;”或“{”结尾的串,比如:
aa 112 … ;
ss {
bb xx {
以上三种形式中的任何一种都算一个“指令串”,其中第一个“单词”(按空格分隔)就是指令名字。
不过这次我们没有使用ngx的默认行为来解析我们的自定义块指令,因为我们并没有打算把块内的数据当成指令,仅仅把它当成普通字符串,所以我们有了如下两行代码:
cf->handler = ngx_my_core_block_handler;
cf->handler_conf = conf;
第一行代替ngx的默认行为,这个方法的形式跟指令对应的回调函数一样,每当解析完我们自定义指令块内的一条“指令串”时就会调用这个方法。
第二个设置的值会传入ngx_my_core_block_handler方法中。
我们写一下ngx_my_core_block_handler方法的实现:
static char *
ngx_my_core_block_handler (ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_uint_t num;
ngx_str_t *words;
// 解析到的“指令串”中“单词”的个数
num = cf->args->nelts;
// 解析到的“指令串”
words = cf->args->elts;
// 向标准输出打印解析到的信息
ngx_log_stderr(0, "find a directive string, num of words[%ui] first word[%s] end word[%s]", num, words[0].data, words[num-1].data);
return NGX_CONF_OK;
}
偷了个懒,只打印了每个指令串的单词个数和第一个单词跟最后一个单词的名字。
另外因为我们在解析块内数据时修改了ngx的默认行为,所以在调用ngx_conf_parse()方法之前要先“保留现场”
save = *cf;
等解析完我们自定义的快内数据后再“还原现场”
*cf = save;
因为在解析其它ngx块指令的时候是不需要设置cf->handler的(比如解析http{}时用的就是默认行为)。
5组合三种约定好的数据
上篇文章曾经说过,不管哪种类型模块,都是用ngx_module_t表示的,约定数据都准备好后就需要用这个结构体来真正声明一个模块,具体如下:
ngx_module_t ngx_my_core_module = {
NGX_MODULE_V1,
&ngx_my_core_module_ctx, /* module context */
ngx_my_core_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
组合好之后我们的自定义核心模块就算写好了,剩下的就是如何把他安装到ngx并运行了。
6注册并运行模块
模块写好之后就要向ngx中注册了,注册之前我们需要先把模块代码组织成如下结构:
ngx_my_core_module
config
ngx_my_core_module.c
其中ngx_my_core_module是一个目录;ngx_my_core_module.c就是写好的源代码;config是一个文件,里面的数据用来描述要注册的模块信息。
我们的模块config文件有如下信息
ngx_addon_name=ngx_my_core_module
CORE_MODULES="$CORE_MODULES ngx_my_core_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_my_core_module.c"
第一行的信息在执行configure的时候会输出到控制台,可以随便写,但不推荐。
第二行是ngx内部要用的模块变量名,这个名字会放到/objs/ngx_modules.c文件中,所以必须是模块声明的变量名(ngx_my_core_module)。
第三行用来指定模块源码路径。
目录建好之后就可以进到ngx源码根目录中进行注册了,假设我们的模块和nginx源码都在/my目录下,则注册步骤如下:
cd /my/nginx-1.9.4
执行configure命令:
./configure –prefix=/my/nginx --add-module=/my/ngx_my_core_module
编译并安装
make & make install
注册好了就可以“运行”模块了,不过在此之前我们先把自定义的指令都配置好,把如下指令配置nginx.conf中:
my_core_simple 111;
my_core_block {
aaa 222;
bbb 111 222 333 444 555;
}
启动ngx后你就会在控制台看到如下信息:
nginx: start runing ngx_my_core_create_conf()
nginx: find a directive name[my_core_simple] value[111] method[ngx_my_core_simple]
nginx: find a directive block name[my_core_block] method[ngx_my_core_block]
nginx: find a directive string, num of words[2] first word[aaa] end word[222]
nginx: find a directive string, num of words[6] first word[bbb] end word[555]
nginx: start runing ngx_my_core_init_conf()
模块注册好了,并且已经可以“运行”了,只不过这个模块的“运行”就是在启动的时候打印一些信息,实际工作中基本没什么用,不过用来学习模块的编写基本够用了,有兴趣的读者可以试着模仿一个更实用的例子。
7附件
为了便于读者学习,这里贴出完整代码
#include <ngx_config.h>
#include <ngx_core.h>
static void * ngx_my_core_create_conf(ngx_cycle_t *cycle);
static char * ngx_my_core_init_conf(ngx_cycle_t *cycle, void *conf);
static char * ngx_my_core_simple(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char * ngx_my_core_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char * ngx_my_core_block_handler (ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_command_t ngx_my_core_commands[] = {
{ ngx_string("my_core_simple"),
NGX_MAIN_CONF| NGX_CONF_TAKE1,
ngx_my_core_simple,
0,
0,
NULL },
{ ngx_string("my_core_block"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_my_core_block,
0,
0,
NULL },
ngx_null_command
};
static ngx_core_module_t ngx_my_core_module_ctx = {
ngx_string("my_core"),
ngx_my_core_create_conf,
ngx_my_core_init_conf
};
ngx_module_t ngx_my_core_module = {
NGX_MODULE_V1,
&ngx_my_core_module_ctx, /* module context */
ngx_my_core_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static void *
ngx_my_core_create_conf(ngx_cycle_t *cycle)
{
ngx_log_stderr(0, "start runing ngx_my_core_create_conf()");
return cycle;
}
static char *
ngx_my_core_init_conf(ngx_cycle_t *cycle, void *conf)
{
ngx_log_stderr(0, "start runing ngx_my_core_init_conf()");
return NULL;
}
static char *
ngx_my_core_simple(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_str_t *name;
name = cf->args->elts;
ngx_log_stderr(0, "find a directive name[%s] value[%s] method[ngx_my_core_simple]",name->data, name[1].data);
return NGX_CONF_OK;
}
static char *
ngx_my_core_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
ngx_conf_t save;
ngx_str_t *name;
name = cf->args->elts;
ngx_log_stderr(0, "find a directive block name[%s] method[ngx_my_core_block]",
name->data);
save = *cf;
cf->handler = ngx_my_core_block_handler;
cf->handler_conf = conf;
rv = ngx_conf_parse(cf, NULL);
*cf = save;
return rv;
}
static char *
ngx_my_core_block_handler (ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_uint_t num;
ngx_str_t *words;
num = cf->args->nelts;
words = cf->args->elts;
ngx_log_stderr(0, "find a directive string, num of words[%ui] first word[%s] end word[%s]",num, words[0].data, words[num-1].data);
return NGX_CONF_OK;
}
相关推荐
Ruoyi 是一个基于 Spring Boot 的开源 Java 框架,旨在简化开发流程,提高开发效率。这个压缩包“ruoyi-common”包含了 Ruoyi 框架的核心通用组件,这些组件在实际项目中起着至关重要的作用。 1. **Spring Boot ...
本文聚焦于提升RFID读写器的性能,特别是群读能力、辐射范围以及读取率,通过设计一款基于Impinj R2000芯片和RFMD公司的RF1604DS芯片的4天线端口读写器核心模块,实现了这一目标。 该核心模块遵循ISO18000-6C等国际...
本模块的发布者“易语言吧”是一个专门收集和分享易语言相关资源的平台,如内部培训课程等,为易语言的学习者和开发者提供了丰富的学习资料和交流空间。 在“素颜_CRC读写模块(1.53更新版.ec)”这个核心文件中,...
这些函数允许一个进程访问另一个进程的内存,实现跨进程内存操作。 首先,你需要了解易语言的模块系统。模块是易语言中可重用代码的单元,它们封装了特定的功能,可以被其他程序或模块调用。在本例中,模块可能包含...
在易语言中,大文件读写是一个常见的需求,尤其在处理大数据、日志分析或者文件备份等场景下。本模块就是针对这种需求而设计的,名为“易语言大文件读写模块”。 该模块的核心功能是优化大文件的处理效率,避免一次...
ET199则可能是该模块的一个特定版本号或者是特定技术的代号。通常,这样的标记意味着该技术经过了多次迭代和优化,达到199阶段,表示在性能、稳定性和安全性上都达到了较高的水平。ET199可能包含了高级的并发控制...
'一个VB写的Base64编码/解码程序的核心编码解码模块VB源码 '本模块包含文件编码解码和纯字符串编码解码函数,需要进行二进制数据编码的请参考文件编码函数。 '因纯字串编码解码时用到 GetTempFileName 获取系统临时...
本文将深入探讨“U盘读写模块”,一个以C语言编写的程序,该程序经过测试并表现出良好的功能特性。 U盘读写模块是计算机软件中的一个关键部分,它负责与硬件层面的USB接口进行通信,执行对U盘数据的读取和写入操作...
5. 其他实用模块:除了上述核心模块,还有许多其他实用模块,如数学计算模块、加密解密模块、时间日期处理模块等。这些模块丰富了易语言的功能,让开发者能够处理各种复杂问题。 在学习易语言模块时,你需要理解每...
"易语言进度写出文件模块"就是这样一个功能组件,它允许程序员在写入文件过程中展示进度,提升程序的交互性。 首先,我们要理解"进度写出文件模块"的核心概念。在文件写入过程中,如果文件较大,一次性全部写入可能...
3. **内存比较**:`memcmp`函数是另一个常见功能,它比较两块内存区域的内容,对于查找或验证内存数据非常有用。 4. **内存初始化**:有时,我们需要清零或填充特定值到新分配的内存,如`memset`函数可以做到这一点...
本模块主要包含以下几个核心功能: 1. **取进程句柄**:获取目标进程的句柄是进行内存操作的前提。通过调用Windows API中的`OpenProcess`函数,我们可以得到指定进程的唯一标识符,这个句柄后续将用于其他内存操作...
1345个易语言模块,易语言模块大集合,够你用的啦 1亦思验证码识别1.5免费版.ec 24位转单色位图模块.ec 32张发牌.ec 3D引擎支持库-eOgre.ec 69msn.ec ACCESS 到高级表格.ec Access操作.ec Access数据库压缩修复新建....
易语言驱动汇编核心库是易语言的一个重要组成部分,它提供了与硬件交互的能力,使得程序可以深入到操作系统内核层面进行操作,例如处理蓝屏错误、实现高效通信以及进行文件读写等关键任务。 蓝屏问题通常是由硬件...
至于"MK模块",这可能是广联达软件中的一个特定模块,可能是用于特定功能的插件或组件,比如自动化计算、数据分析等。制作修改MK模块意味着用户或开发人员可以定制化这个模块以适应自己的工作需求,提升工作效率。 ...
设计一个射频超小型UHF RFID读写模块通常需要考虑以下几个方面: 1. 工作频段与通信协议:根据应用需求选择合适的UHF频段(例如860-960MHz),并确保读写模块符合国际标准化组织定义的通信协议,如ISO/IEC 18000-6C...
在本文中,我们将深入探讨一个名为“Project2”的多周期MIPS处理器的核心模块。这个处理器设计包括了多个关键组件,每个组件都有特定的功能,共同协作以执行MIPS指令集的42种指令。 首先,我们关注的是“flopr”...
首先,CH375B芯片是整个模块的核心,它是一个高度集成的USB接口控制器,支持USB2.0 Full Speed规范,能够处理与USB设备的所有通信事务。该芯片具有内置的USB协议解析功能,能够识别和响应USB主机的各种请求,同时也...
搜集和整理的1300多个易语言模块,易语言模块大集合: 文件拖放.ec 文件拖放_YE.ec 文件拖放_叶如兆.ec 文件时间操作模块1.0.ec 文件补丁模块.ec 文件读写&文件映射模块.ec 文字背景透明.ec 文本分行.EC 文本分行1.0...
### Android核心模块分析及相关技术 #### 操作系统层(OS) Android的核心模块之一是操作系统层,采用的是Linux 2.6作为基础操作系统。Linux 2.6是一个成熟且开源的操作系统,为Android提供了稳定性和安全性。该层...