前言
如果接着上两篇笔记走的话,现在就应该说worker循环工作中的核心处理方法ngx_process_events_and_timers()。但是在看这个功能函数之前,需要先学习相关的事件模块内容。这一块也算是我学习Nginx的核心了,里面有许多比较有趣的内容,比如常见的负载均衡,“惊群”现象等等。当然,最想学习的还是Nginx中关于epoll事件驱动机制了。
事件处理框架
Nginx事件处理框架主要围绕两种事件进行,分别是是网络事件以及定时器事件。其中,网络事件主要就是以TCP事件为主了。Nginx的高可移植性决定了它能够在不同的操作系统内核中选择符合的事件驱动机制支持网络事件的处理。Nginx支持的事件驱动机制可以看《笔记十一》中的模块间关系图。
那Nginx的事件处理框架又是如何选择事件驱动机制的呢?
1) Nginx定义了一个核心模块ngx_events_module,在Nginx启动时调用ngx_init_cycle方法解析配置项,找到"events{}"配置项后,ngx_events_module模块即开始工作。它的主要工作就是为所有的事件模块解析"events{}"中的配置项,同时管理这些事件模块存储配置项的结构体;
2) Nginx定义了一个非常重要的事件模块ngx_event_core_module,该模块决定使用哪种事件驱动机制,以及如何管理事件(后续笔记详细介绍);
3) Nginx定义了一系列运行在不同操作系统、不同内核版本上的事件驱动机制,包括:ngx_epoll_module、ngx_kqueue_module、ngx_poll_module、ngx_select_module、ngx_devpoll_module、ngx_eventport_module、ngx_aio_module、ngx_rtsig_module和基于Windows的ngx_kqueue_module模块。在ngx_event_core_module模块中,将会从以上9个模块中选择1个作为Nginx进程的事件驱动模块。
事件模块结构
事件模块是一种新的模块类型,它的通用接口是ngx_event_module_t结构体:
typedef struct {
ngx_str_t *name; // 事件模块名称
/* 创建存储events{}中配置项的结构体 */
void *(*create_conf)(ngx_cycle_t *cycle);
/* 初始化操作,用以处理事件模块感兴趣的配置项 */
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
/* 重要!每个事件模块需要实现10种抽象方法 */
ngx_event_actions_t actions;
} ngx_event_module_t;
下面是ngx_evnet_actions_t的实现:
typedef struct {
/* 添加/删除事件,负责在事件驱动机制里(如epoll中)添加/删除 感兴趣的事件 */
ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
/* 启动/禁用事件,目前不会调用此2种方法,代码中实现方式跟添加/删除一致 */
ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
/* 添加/删除新的连接,复杂在事件驱动机制里(如epoll)添加/删除新的连接,
* 这意味着新连接上的读写事件都将被事件驱动关注
*/
ngx_int_t (*add_conn)(ngx_connection_t *c);
ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
/* 具体的事件处理方法 */
ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
ngx_uint_t flags);
/* 事件驱动模块的初始化及结束方法 */
ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
Nginx事件定义
说实话,这种大型的结构体成员如果不是亲自去实现,我也不知道怎么学习。这种摘抄式的笔记,我也不知道用处有多大,抄一遍加深下印象?目前看,就这种意思吧。
struct ngx_event_s {
/* 事件相关的对象,通常指向ngx_connection_t连接对象 */
void *data;
/* 标志位,标识事件可写,意味着对应的TCP连接可写,也即连接处于发送网络包状态 */
unsigned write:1;
/* 标志位,标识可建立新的连接,一般是在ngx_listening_t对应的读事件中标记 */
unsigned accept:1;
/* used to detect the stale events in kqueue, rtsig, and epoll */
/* 用于区分当前事件是否过期,这涉及到一个很好的小技巧,后续会专门介绍 */
unsigned instance:1;
/*
* the event was passed or would be passed to a kernel;
* in aio mode - operation was posted.
*/
/* 标志位,标识当前事件是否活跃 */
unsigned active:1;
/* 标志位,标识当前事件是否禁用 */
unsigned disabled:1;
/* the ready event; in aio mode 0 means that no operation can be posted */
/* 标志位,标识当前事件是否准备好,为1时表示可以被相应消费模块进行处理 */
unsigned ready:1;
/* 对epoll来说无意义,不笔记,我也不懂... */
unsigned oneshot:1;
/* aio operation is complete */
/* 标志位,用于AIO异步事件的处理 */
unsigned complete:1;
unsigned eof:1; // 当前处理的字符流结束符
unsigned error:1; // 事件处理错误
unsigned timedout:1; // 超时标识
unsigned timer_set:1; // 标志该事件处于定时器中
unsigned delayed:1; // 需要延迟处理事件
unsigned deferred_accept:1; // 未使用
/* the pending eof reported by kqueue, epoll or in aio chain operation */
unsigned pending_eof:1;
#if !(NGX_THREADS)
unsigned posted_ready:1; // 表示处理post事件时,当前事件准备就绪
#endif
#if (NGX_WIN32)
/* setsockopt(SO_UPDATE_ACCEPT_CONTEXT) was successful */
unsigned accept_context_updated:1;
#endif
#if (NGX_HAVE_KQUEUE)
unsigned kq_vnode:1;
/* the pending errno reported by kqueue */
int kq_errno;
#endif
/*
* kqueue only:
* accept: number of sockets that wait to be accepted
* read: bytes to read when event is ready
* or lowat when event is set with NGX_LOWAT_EVENT flag
* write: available space in buffer when event is ready
* or lowat when event is set with NGX_LOWAT_EVENT flag
*
* iocp: TODO
*
* otherwise:
* accept: 1 if accept many, 0 otherwise
*/
#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
int available;
#else
unsigned available:1; // 在epoll机制下,标识一次尽可能多的建立TCP连接
#endif
/* 事件发生时的处理函数,每个事件消费模块都会重新实现它 */
ngx_event_handler_pt handler;
#if (NGX_HAVE_AIO)
#if (NGX_HAVE_IOCP)
ngx_event_ovlp_t ovlp;
#else
struct aiocb aiocb;
#endif
#endif
ngx_uint_t index;
ngx_log_t *log;
ngx_rbtree_node_t timer; // 定时器节点,用于定时器红黑树中
unsigned closed:1;
/* to test on worker exit */
unsigned channel:1;
unsigned resolver:1;
#if (NGX_THREADS)
unsigned locked:1;
unsigned posted_ready:1;
unsigned posted_timedout:1;
unsigned posted_eof:1;
#if (NGX_HAVE_KQUEUE)
/* the pending errno reported by kqueue */
int posted_errno;
#endif
#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
int posted_available;
#else
unsigned posted_available:1;
#endif
ngx_atomic_t *lock;
ngx_atomic_t *own_lock;
#endif
/* the links of the posted queue */
/* post事件双向链表 */
ngx_event_t *next;
ngx_event_t **prev;
#if 0
/* the threads support */
/*
* the event thread context, we store it here
* if $(CC) does not understand __thread declaration
* and pthread_getspecific() is too costly
*/
void *thr_ctx;
#if (NGX_EVENT_T_PADDING)
/* event should not cross cache line in SMP */
uint32_t padding[NGX_EVENT_T_PADDING];
#endif
#endif
}
每一个事件最核心的部分就是handler回调方法,它将由每一个事件消费模块实现,以此决定这个事件究竟如何“消费”。原型如下:
typedef void (*ngx_event_handler_pt)(ngx_event_t *ev);
所有Nginx模块只要进行事件处理就必然要设置handler回调方法。
那么如何Nginx是如何往事件驱动模块中添加/删除感兴趣的事件呢?
答案:
1) Nginx封装了两个简单的方法用于在事件驱动模块中添加或删除事件;
2) 调用上面提到的ngx_event_actions_t中的add或del抽象方法;(书中表示不推荐)
现在可以看看Nginx封装的两个方法:
- ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags);
该方法负责将读事件添加到事件驱动模块中,rev是要操作的事件,flags指定事件的驱动方式。以epoll驱动机制为例,flags取值为0或者NGX_CLOSE_EVENT,Nginx主要工作在ET模式下,一般可以忽略flags参数。返回值NGX_OK或NGX_ERROR。
- ngx_int_t ngx_handle_write_event(ngx_event_t *wev, size_t lowat);
同上,该方法表示将写事件添加到事件驱动模块中,wev表示要操作的事件,lowat表示只有当连接对应的套接字缓冲区必须有lowat大小的可用空间时,才能处理这个可写事件。
另外,关于ngx_event_actions_t中的事件设置方法,直接使用时都会与具体的事件驱动机制强相关,使用上述两种封装方法可以屏蔽不同机制的差异。
#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
总结
这个基本属于对事件驱动机制的介绍吧。之前将事件模块章看了一遍,现在在重头摘抄做下笔记,加深印象吧。
主要参考
《深入理解Nginx》