9.2 Nginx事件的定义

在Nginx中,每一个事件都由ngx_event_t结构体来表示。本节说明ngx_event_t中每一个成员的含义,如下所示。


typedef struct ngx_event_s ngx_event_t;

struct ngx_event_s{

/事件相关的对象。通常data都是指向ngx_connection_t连接对象。开启文件异步I/O时,它可能会指向ngx_event_aio_t结构体/

void*data;

/标志位,为1时表示事件是可写的。通常情况下,它表示对应的TCP连接目前状态是可写的,也就是连接处于可以发送网络包的状态/

unsigned write:1;

/标志位,为1时表示为此事件可以建立新的连接。通常情况下,在ngx_cycle_t中的listening动态数组中,每一个监听对象ngx_listening_t对应的读事件中的accept标志位才会是1/

unsigned accept:1;

/这个标志位用于区分当前事件是否是过期的,它仅仅是给事件驱动模块使用的,而事件消费模块可不用关心。为什么需要这个标志位呢?当开始处理一批事件时,处理前面的事件可能会关闭一些连接,而这些连接有可能影响这批事件中还未处理到的后面的事件。这时,可通过instance标志位来避免处理后面的已经过期的事件。在9.6节中,将详细描述ngx_epoll_module是如何使用instance标志位区分过期事件的,这是一个巧妙的设计方法/

unsigned instance:1;

/标志位,为1时表示当前事件是活跃的,为0时表示事件是不活跃的。这个状态对应着事件驱动模块处理方式的不同。例如,在添加事件、删除事件和处理事件时,active标志位的不同都会对应着不同的处理方式。在使用事件时,一般不会直接改变active标志位/

unsigned active:1;

/标志位,为1时表示禁用事件,仅在kqueue或者rtsig事件驱动模块中有效,而对于epoll事件驱动模块则无意义,这里不再详述/

unsigned disabled:1;

/标志位,为1时表示当前事件已经准备就绪,也就是说,允许这个事件的消费模块处理这个事件。在HTTP框架中,经常会检查事件的ready标志位以确定是否可以接收请求或者发送响应/

unsigned ready:1;

/该标志位仅对kqueue,eventport等模块有意义,而对于Linux上的epoll事件驱动模块则是无意义的,限于篇幅,不再详细说明/

unsigned oneshot:1;

//该标志位用于异步AIO事件的处理,在9.9节中会详细描述

unsigned complete:1;

//标志位,为1时表示当前处理的字符流已经结束

unsigned eof:1;

//标志位,为1时表示事件在处理过程中出现错误

unsigned error:1;

/标志位,为1时表示这个事件已经超时,用以提示事件的消费模块做超时处理,它与timer_set都用于9.7节将要介绍的定时器/

unsigned timedout:1;

//标志位,为1时表示这个事件存在于定时器中

unsigned timer_set:1;

//标志位,delayed为1时表示需要延迟处理这个事件,它仅用于限速功能

unsigned delayed:1;

//该标志位目前没有使用

unsigned read_discarded:1;

//标志位,目前这个标志位未被使用

unsigned unexpected_eof:1;

/标志位,为1时表示延迟建立TCP连接,也就是说,经过TCP三次握手后并不建立连接,而是要等到真正收到数据包后才会建立TCP连接/

unsigned deferred_accept:1;

/标志位,为1时表示等待字符流结束,它只与kqueue和aio事件驱动机制有关,不再详述/

unsigned pending_eof:1;

if!(NGX_THREADS)

//标志位,如果为1,则表示在处理post事件时,当前事件已经准备就绪

unsigned posted_ready:1;

endif

/标志位,在epoll事件驱动机制下表示一次尽可能多地建立TCP连接,它与multi_accept配置项对应,实现原理参见9.8.1节/

unsigned available:1;

//这个事件发生时的处理方法,每个事件消费模块都会重新实现它

ngx_event_handler_pt handler;

if(NGX_HAVE_AIO)

if(NGX_HAVE_IOCP)

//Windows系统下的一种事件驱动模型,这里不再详述

ngx_event_ovlp_t ovlp;

else

//Linux aio机制中定义的结构体,在9.9节中会详细说明它

struct aiocb aiocb;

endif

endif

//由于epoll事件驱动方式不使用index,所以这里不再说明

ngx_uint_t index;

//可用于记录error_log日志的ngx_log_t对象

ngx_log_t*log;

//定时器节点,用于定时器红黑树中,在9.7节会详细介绍

ngx_rbtree_node_t timer;

//标志位,为1时表示当前事件已经关闭,epoll模块没有使用它

unsigned closed:1;

//该标志位目前无实际意义

unsigned channel:1;

//该标志位目前无实际意义

unsigned resolver:1;

/post事件将会构成一个队列再统一处理,这个队列以next和prev作为链表指针,以此构成一个简易的双向链表,其中next指向后一个事件的地址,prev指向前一个事件的地址/

ngx_event_t

*next;

ngx_event_t

**prev;

};


每一个事件最核心的部分是handler回调方法,它将由每一个事件消费模块实现,以此决定这个事件究竟如何“消费”。下面来看一下handler方法的原型,代码如下。


typedef void(ngx_event_handler_pt)(ngx_event_tev);


所有的Nginx模块只要处理事件就必然要设置handler回调方法,后续章节会有许多handler回调方法的例子,这里不再详述。

下面开始说明操作事件的方法。

事件是不需要创建的,因为Nginx在启动时已经在ngx_cycle_t的read_events成员中预分配了所有的读事件,并在write_events成员中预分配了所有的写事件。事实上,从图9-1中我们会看到每一个连接将自动地对应一个写事件和读事件,只要从连接池中获取一个空闲连接就可以拿到事件了。那么,怎么把事件添加到epoll等事件驱动模块中呢?需要调用9.1.1节中提到的ngx_event_actions_t结构体的add方法或者del方法吗?答案是Nginx为我们封装了两个简单的方法用于在事件驱动模块中添加或者移除事件,当然,也可以调用ngx_event_actions_t结构体的add或者del等方法,但并不推荐这样做,因为Nginx提供的ngx_handle_read_event和ngx_handle_write_event方法还是做了许多通用性的工作的。

先看一下ngx_handle_read_event方法的原型:


ngx_int_t ngx_handle_read_event(ngx_event_t*rev,ngx_uint_t flags);


ngx_handle_read_event方法会将读事件添加到事件驱动模块中,这样该事件对应的TCP连接上一旦出现可读事件(如接收到TCP连接另一端发送来的字符流)就会回调该事件的handler方法。

下面看一下ngx_handle_read_event的参数和返回值。参数rev是要操作的事件,flags将会指定事件的驱动方式。对于不同的事件驱动模块,flags的取值范围并不同,本书以Linux下的epoll为例,对于ngx_epoll_module来说,flags的取值范围可以是0或者NGX_CLOSE_EVENT(NGX_CLOSE_EVENT仅在epoll的LT水平触发模式下有效),Nginx主要工作在ET模式下,一般可以忽略flags这个参数。该方法返回NGX_OK表示成功,返回NGX_ERROR表示失败。

再看一下ngx_handle_write_event方法的原型:


ngx_int_t ngx_handle_write_event(ngx_event_t*wev,size_t lowat);


ngx_handle_write_event方法会将写事件添加到事件驱动模块中。wev是要操作的事件,而lowat则表示只有当连接对应的套接字缓冲区中必须有lowat大小的可用空间时,事件收集器(如select或者epoll_wait调用)才能处理这个可写事件(lowat参数为0时表示不考虑可写缓冲区的大小)。该方法返回NGX_OK表示成功,返回NGX_ERROR表示失败。

一般在向epoll中添加可读或者可写事件时,都是使用ngx_handle_read_event或者ngx_handle_write_event方法的。对于事件驱动模块实现的ngx_event_actions结构体中的事件设置方法,最好不要直接调用,下面这4个方法直接使用时都会与具体的事件驱动机制强相关,而使用ngx_handle_read_event或者ngx_handle_write_event方法则可以屏蔽这种差异。


define ngx_add_event

ngx_event_actions.add

define ngx_del_event

ngx_event_actions.del

define ngx_add_conn

ngx_event_actions.add_conn

define ngx_del_conn

ngx_event_actions.del_conn