接上文。
这一节来看看nginx是如何封装epoll构筑整个事件驱动模块的,主要参看了《nginx从入门到精通》 http://tengine.taobao.org/book/index.html 、《nginx模块开发与架构解析》和官方的一些资源http://wiki.nginx.org/Resources。
先不去看源码,想想假如我们用epoll来实现服务器会怎么做。
不知道其他人怎么想,我在上面的描述中已经重点区分了新连接的建立和已有连接上的新数据到达两个事件。网上流行的几乎所有例子都是在一个循环里,直接if else处理这些事件,但我一直觉得很别扭,直到看了nginx源码才发现这样真的不太好。虽然一般连接建立,就预示新数据要到达,而且新连接本质上就是新数据到达,但从概念上这的确是两个不同的东西,同一个连接,新连接的数据只会有一次,而后可以有很多次的数据读写,这些都和连接无关,不在一个逻辑概念层次上。
先不说这个了,上面只是一家之言。nginx的事件模块本身就是一个模块,而nginx设计的核心就是配置和模块,因为对源码还不够熟悉,所以目前还没有开始涉及这一块,但此处有必要简单地描述nginx的模块架构。在nginx里,模块的定义在ngx_conf_file.h文件中。
struct ngx_module_s {
ngx_uint_t ctx_index;
ngx_uint_t index;
void *ctx;
ngx_command_t *commands;
ngx_uint_t type;
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
};
看上面的结构体,在前面的文章中,已经在系统启动过程中打印过其中的ctx_index、index、type和commands中的name属性了。ngx_command_s也定义在ngx_conf_file.h中文件内。
struct ngx_command_s {
ngx_str_t name;
int type;
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
int conf;
int offset;
void *post;
};
对C语言不太熟悉的人看到这两个结构体可能没什么感觉,而且上C语言课的时候也没有人这么说过,但还是有一些资料上会提到,这样的结构体本质上就是一个类,而不仅仅是平常印象里用来存储数据的单元,结构体远比存储单元所做的事情要多,因为这里不仅可以定义指向其他结构体的指针,比如ngx_command_s,还可以定义函数指针。其实这就相当于C++或Java中的类,只是没有属性限定符。理清这节,再来看这两个结构体就比较容易理解了,两个不同的类型里各自定义了一些属性,和可以执行的方法。
ngx_module_s中除了index和type外,有个ngx_command_s类型的熟悉,定义的是这个模块的指令(command翻译成命令,但很多文献上又用directive来说明,不知道当初nginx作者为什么不直接用directive来命名),而指令的实际意义就是nginx.conf文件中以行为单位的配置项(区别于以{}为单元的配置项)。这一点就给了每个模块在配置上很大的自由,因为每个模块可以有自己的配置指令。再看另外两个方法,init_module和init_process,很显然这是两个初始化的方法,前者在init_cycle中调用,主要分配空间,后者在process_cycle中调用,主要对该模块做各种设置。
ngx_command_s中的就不细说了,总之是与存取和解析指令相关的。本文重点关注事件模块。提到模块的架构,是因为这里涉及到了事件模块的初始化。当nginx启动时,对所有模块进行初始化的时候,事件模块也在其中。
由于ngx_module_s是一个类,这个“类“就得“实例化“,在nginx这个“实例化“就在ngx_event.c文件中。
ngx_module_t ngx_events_module = {
NGX_MODULE,
&ngx_events_module_ctx, /* module context */
ngx_events_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init module */
NULL /* init process */
};
严格和非严格地说,这里都算不上实例化,因为它形式上就是赋值,但因为在c语言的系统里,它这样定义就是一个全局变量,而在此,它的值基本不可能会变,所以就当作是实例化,但又因为,如果把C语言的结构体以类的形式来看,它的属性就不如方法那么重要了,而此处的两个方法都是NULL,也就是空的方法,用C++类比就是方法未定义,相当于抽象类或接口,所以也就谈不上“实例化“了。
无法实例化,它就只能当一个接口存储一些标志,而实际完成工作的有另外一个实例,在ngx_event.c文件中:
ngx_module_t ngx_event_core_module = {
NGX_MODULE,
&ngx_event_core_module_ctx, /* module context */
ngx_event_core_commands, /* module directives */
NGX_EVENT_MODULE, /* module type */
ngx_event_module_init, /* init module */
ngx_event_process_init /* init process */
};
从名字看,它是事件核心模块,而且定义了初始化方法,当事件模块初始化的时候,就会调用这两个方法,在其中完成相应的设置操作。
在上面的两个结构体里都有一个ctx是前面没有提到的,这本身是个空指针类型,两处都赋上了相应的值,前者是:
static ngx_core_module_t ngx_events_module_ctx = {
ngx_string("events"),
NULL,
NULL
};
后者是:
ngx_event_module_t ngx_event_core_module_ctx = {
&event_core_name,
ngx_event_create_conf, /* create configuration */
ngx_event_init_conf, /* init configuration */
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};
两个变量都在ngx_event.c文件中,前者是static类型只在当前文件范围内有效,后者是全局变量且是ngx_event_module_t类型,定义在ngx_event.h文件中:
typedef struct {
ngx_str_t *name;
void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
ngx_event_actions_t actions;
} ngx_event_module_t;
其中有两个方法,和一个结构体ngx_event_actions_t,而ngx_event_actions_t结构体也在ngx_event.h中,完全就是一个方法的接口:
typedef struct {
ngx_int_t (*add)(ngx_event_t *ev, int event, u_int flags);
ngx_int_t (*del)(ngx_event_t *ev, int event, u_int flags);
ngx_int_t (*enable)(ngx_event_t *ev, int event, u_int flags);
ngx_int_t (*disable)(ngx_event_t *ev, int event, u_int flags);
ngx_int_t (*add_conn)(ngx_connection_t *c);
ngx_int_t (*del_conn)(ngx_connection_t *c, u_int flags);
ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t try);
ngx_int_t (*process_events)(ngx_cycle_t *cycle);
ngx_int_t (*init)(ngx_cycle_t *cycle);
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
这些方法都很容易理解,都是对于事件操作的函数。至此,nginx事件模型的所有结构体都齐全了,但还没有考虑epoll,因为nginx要考虑的跨平台移植性,所以对于不同的平台还有相应的模块来完成实际的功能,也就是说,前面定义的结构体相当与是一个模块,加上管理功能,管理具体的事件(做事情的)模块,在这里是epoll,所以在epoll模块里也会有一套完整的定义。从形式上看,epoll模块定义了前面结构体中为空的那些函数指针的值,设置了相应的函数,而前面已有的则反设为空,这非常类似类的继承,父类定义了一些实现了的方法和虚函数,子类继承已有的方法和实现自己的函数。
至此,结构方面的内容介绍完了,下面看流程。系统启动时事件模块遵循整理启动流程,同时,其自身流程如下
rev->event_handler = &ngx_event_accept;
rev->event_handler = ngx_http_init_request;
上面基本把nginx的事件处理串起来了,但还有两点:
to be continued…