Nginx 是一个高性能的 HTTP 和反向代理服务器,同时也是一个 IMAP/POP3 代理服务器。它的核心是其事件驱动的架构,这使得 Nginx 能够处理大量的并发连接。Nginx 的事件模块是实现这一高性能的关键部分。
1. 引言
Nginx 作为高性能 Web 服务器,其核心在于非阻塞、异步的事件驱动模型。而这一切都建立在 ngx_event_module 这套精巧的内部基础设施之上。很多开发者熟悉 Nginx 的配置,却对底层事件处理机制一知半解,导致自己编写的模块性能不佳或行为诡异。
本教程将带你直接从源代码出发,剖析 Nginx event 模块的工作原理,并配合实际代码示例,详细讲解 I/O 读写事件、定时器与超时的注册、触发和管理。读完后,你将有能力编写稳定高效的 Nginx 事件驱动模块。
2. Nginx 事件驱动架构一览
在开始源码之旅前,我们先通过一张流程图建立整体认知:
Nginx 工作进程的核心循环就是反复调用 ngx_process_events_and_timers(),该函数内部完成:
-
调用平台相关的事件收集器(
epoll_wait/kqueue等) -
遍历就绪事件并执行对应的回调
handler - 处理定时器红黑树,执行超时事件
下一节我们将聚焦事件模块的核心数据结构,理解这一切是如何串联起来的。
3. 事件模块核心数据结构(源码剖析)
3.1 事件基类:ngx_event_t
在 src/event/ngx_event.h 中定义了整个事件系统的灵魂:
struct ngx_event_s {
void *data; // 通常指向 ngx_connection_t
unsigned write:1; // 1 = 写事件,0 = 读事件
unsigned accept:1; // 标记为 accept 事件
unsigned instance:1; // 用于检测过期事件
unsigned active:1; // 是否已加入事件收集器
unsigned disabled:1;
unsigned ready:1; // 事件就绪标记
unsigned oneshot:1;
unsigned complete:1;
unsigned eof:1;
unsigned error:1;
unsigned timedout:1; // 超时标记
unsigned timer_set:1; // 是否已加入定时器红黑树
unsigned delayed:1;
unsigned deferred_accept:1;
unsigned pending_eof:1;
unsigned posted:1;
ngx_int_t index; // 模块索引上下文
ngx_log_t *log;
ngx_rbtree_node_t timer; // 定时器红黑树节点
ngx_queue_t queue; // 用于 post 链表
ngx_event_handler_pt handler; // 回调函数指针
};
逐字段解释:
-
data:关联的ngx_connection_t,这样在回调中就能极快地拿到连接上下文 -
active:该事件是否已被添加到 epoll 中。防止重复添加 -
ready:当 epoll_wait 返回后设为 1,表示该事件已就绪 -
timedout:当超时发生时(例如读取客户端请求太慢),框架将该标志置 1,回调里检测到此标志后可终止或清理 -
timer_set:该事件是否已挂载到红黑树中 -
handler:这才是我们要关注的重点——事件就绪时执行的函数指针
3.2 连接封装:ngx_connection_t
每个 TCP 连接都对应一个 ngx_connection_t,其中内嵌了两个事件:
struct ngx_connection_s {
void *data;
ngx_event_t *read; // 读事件
ngx_event_t *write; // 写事件
ngx_socket_t fd;
ngx_recv_pt recv;
ngx_send_pt send;
// ...
};
关键点:read->handler 和 write->handler 即我们模块中需要设置的读写回调函数。Nginx 在就绪时会自动调用。
4. 事件处理主循环:ngx_process_events_and_timers
该函数位于 src/event/ngx_event.c,是整个事件引擎的调度中心。简化后的流程如下:
void ngx_process_events_and_timers(ngx_cycle_t *cycle) {
ngx_uint_t flags;
ngx_msec_t timer;
// 1. 从红黑树获得最近超时时间
timer = ngx_event_find_timer();
// 2. 调用平台实现,等待 IO 事件
(void) ngx_process_events(cycle, timer, flags);
// 3. 执行延迟的事件链表(posted events)
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
ngx_event_process_posted(cycle, &ngx_posted_events);
// 4. 处理超时事件
ngx_event_expire_timers();
}
这里的 ngx_process_events 实际上是一个宏,最终调用的是平台相关的 ngx_event_actions.process_events,在 Linux 下即 ngx_epoll_process_events。整个过程清晰诠释了事件驱动的精髓:等待 I/O → 处理 I/O → 处理超时 → 循环。
5. I/O 读写事件详解
5.1 读事件:从 fd 读取数据
读事件主要用于接收客户端数据或 upstream 响应。通常的注册流程为:
// 假设 c 是已经建立好的 ngx_connection_t *c
c->read->handler = ngx_http_my_read_handler; // 自定义回调
// 将事件添加到 epoll
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
ngx_close_connection(c);
return;
}
在回调 ngx_http_my_read_handler 中,我们需要自己处理具体的 recv 以及连接管理:
static void ngx_http_my_read_handler(ngx_event_t *rev) {
ssize_t n;
ngx_connection_t *c = rev->data;
u_char buf[1024];
if (rev->timedout) { // 检查超时标志
ngx_log_error(NGX_LOG_INFO, c->log, 0, "read timeout");
ngx_close_connection(c);
return;
}
n = c->recv(c, buf, sizeof(buf));
if (n == NGX_AGAIN) {
return; // 暂无数据,等待下次通知
}
if (n == NGX_ERROR || n == 0) {
ngx_close_connection(c);
return;
}
// 处理接收到的 buf ...
// 然后重新添加读事件(如果还想继续读)
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_close_connection(c);
}
}
注意:rev->timedout 标志由事件框架在 ngx_event_expire_timers() 中设置,这是检测 I/O 超时的标准方法。如果你的模块没有任何读活跃,框架会自动触发该事件的 handler 并置 timedout=1。
5.2 写事件:将数据发送出去
写事件的触发时机是在数据无法一次写完(socket 缓冲区满)时,由 epoll 通知我们可以继续写。以发送一段缓冲区为例:
static void ngx_http_my_write_handler(ngx_event_t *wev) {
ssize_t n, size;
ngx_connection_t *c = wev->data;
if (wev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "write timeout");
ngx_close_connection(c);
return;
}
// 假设 my_buf 是需要发送的 ngx_buf_t
size = ngx_buf_size(my_buf);
n = c->send(c, my_buf->pos, size);
if (n == NGX_AGAIN) {
return;
}
if (n == NGX_ERROR) {
ngx_close_connection(c);
return;
}
my_buf->pos += n;
if (n == size) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "write finished");
// 删除写事件:写完了就不再需要监听可写
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
ngx_close_connection(c);
return;
}
// ... 后续处理,例如关闭连接或等待下一个请求
}
}
初读这里可能会困惑:为什么要删除写事件? 因为 epoll 默认是水平触发(LT),只要缓冲区不满就会反复通知可写,造成 CPU 空转。所以 Nginx 推荐:只在需要写入数据时才临时添加写事件,写完后立刻删除。
6. 定时器与超时机制
6.1 设置超时
每个 ngx_event_t 都内嵌了一个红黑树节点 timer,只需设置超时毫秒数并调用添加接口即可:
// 为读事件设置 30 秒超时 ngx_add_timer(c->read, 30000);
或者使用更通用的宏 ngx_event_add_timer(ev, timer),作用相同。当时间到达后,框架会在主循环中自动调用 handler 并将 ev->timedout 设为 1。我们只需在回调里检查该标志。
6.2 取消超时
在事件正常完成或主动关闭连接前,务必取消定时器,否则可能导致悬挂指针:
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
6.3 两种超时场景
-
连接空闲超时:长时间无数据交互。可通过
ngx_add_timer设置,在 read handler 中检测timedout后关闭连接。 -
写超时:发送数据对端迟迟不收包。同样在 write handler 中检查
timedout,防止资源泄漏。
7. 实战示例:一个最简单的事件驱动模块
下面我们编写一个 Nginx 模块,它会在接收到 HTTP 请求后,先睡眠模拟耗时操作,然后返回响应。通过这个例子,你将完整看到读事件、定时器、写事件的组合用法。
7.1 模块配置与上下文
typedef struct {
ngx_msec_t delay; // 模拟延迟时间(毫秒)
} ngx_http_delay_loc_conf_t;
在配置解析时获取 delay 指令。模块指令定义略。
7.2 请求处理入口
static ngx_int_t
ngx_http_delay_handler(ngx_http_request_t *r)
{
ngx_http_delay_loc_conf_t *dlcf;
// 非 GET 请求不处理
if (r->method != NGX_HTTP_GET) {
return NGX_DECLINED;
}
dlcf = ngx_http_get_module_loc_conf(r, ngx_http_delay_module);
// 创建一个定时事件,延迟后写响应
ngx_event_t *ev = ngx_pcalloc(r->pool, sizeof(ngx_event_t));
if (ev == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ev->data = r;
ev->handler = ngx_http_delay_timeout_handler;
ev->log = r->connection->log;
ngx_add_timer(ev, dlcf->delay);
// 注意:这里返回 NGX_DONE 告诉 Nginx 不要完成请求,后续由我们手动调用 ngx_http_finalize_request
return NGX_DONE;
}
7.3 定时器回调(生成响应并发送)
static void
ngx_http_delay_timeout_handler(ngx_event_t *ev)
{
ngx_http_request_t *r = ev->data;
ngx_buf_t *b;
ngx_chain_t out;
// 构造响应体
b = ngx_create_temp_buf(r->pool, 1024);
if (b == NULL) {
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
b->last = ngx_sprintf(b->last, "Delay response after %M ms\n",
(ngx_msec_t) ev->timer.key); // 简化 demo 使用 key 演示
// 设置响应头
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = b->last - b->pos;
ngx_http_send_header(r);
out.buf = b;
out.next = NULL;
// 发送响应,并设置写事件处理(如有必要)
ngx_int_t rc = ngx_http_output_filter(r, &out);
if (rc == NGX_AGAIN || rc == NGX_ERROR) {
ngx_http_finalize_request(r, rc);
return;
}
// 主动完成请求
ngx_http_finalize_request(r, NGX_OK);
}
7.4 结合读超时的健壮版写法
上面的例子仅为演示定时器。在实际网络通信中,我们往往需要在读写事件上叠加超时。以读取客户端额外数据为例:
// 某读事件回调
static void
my_read_handler(ngx_event_t *rev)
{
ngx_connection_t *c = rev->data;
if (rev->timedout) {
ngx_log_error(NGX_LOG_ERR, c->log, 0, "client read timeout");
ngx_close_connection(c);
return;
}
// 读取并处理...
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_close_connection(c);
return;
}
// 续订超时(重置30秒计时)
ngx_del_timer(rev);
ngx_add_timer(rev, 30000);
}
这样客户端若在 30 秒内无新数据,timedout 就会被置位,连接得以安全关闭。
9. 总结编译与配置
前面我们实现了延迟响应模块的核心逻辑,本节将补全模块的编译与集成细节,让你能够真正跑起来。
8.1 config 文件
Nginx 的第三方模块通过一个 config 文件告知构建系统模块名称、源文件集合。在项目根目录下创建 ngx_http_delay_module/ 文件夹,并新建 config 文件:
ngx_addon_name=ngx_http_delay_module HTTP_MODULES="$HTTP_MODULES ngx_http_delay_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_delay_module.c"
-
ngx_addon_name:模块名称,通常与目录名一致。 -
HTTP_MODULES:将该模块追加到 HTTP 模块链表中,Nginx 才能在启动时加载它。 -
NGX_ADDON_SRCS:需要编译的源文件,路径中的$ngx_addon_dir会被自动展开为当前模块目录。
如果需要支持动态模块,可在 config 中额外添加:
if [ -n "$auto_configure" ]; then
have=NGX_HTTP_DELAY . auto/have
fi
本文以静态编译为主,动态编译部分可参考 Nginx 官方文档。
8.2 模块结构定义与指令解析
将以下完整代码追加到 ngx_http_delay_module.c 中,即可完成模块注册与指令解析(前文的 handler 等函数保留在原文件中):
/* 模块指令定义 */
static ngx_command_t ngx_http_delay_commands[] = {
{ ngx_string("delay"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_http_delay,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
/* 模块上下文 */
static ngx_http_module_t ngx_http_delay_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_delay_create_loc_conf, /* create location configuration */
ngx_http_delay_merge_loc_conf /* merge location configuration */
};
/* 模块定义 */
ngx_module_t ngx_http_delay_module = {
NGX_MODULE_V1,
&ngx_http_delay_module_ctx, /* module context */
ngx_http_delay_commands, /* module directives */
NGX_HTTP_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
};
紧接着实现指令解析函数与 location 配置的创建/合并函数:
/* delay 指令解析函数 */
static ngx_int_t
ngx_http_delay(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_delay_loc_conf_t *dlcf = conf;
ngx_str_t *value;
ngx_int_t ms;
value = cf->args->elts;
ms = ngx_parse_time(&value[1], 0);
if (ms == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid value \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}
dlcf->delay = (ngx_msec_t) ms;
return NGX_CONF_OK;
}
/* 创建 location 配置 */
static void *
ngx_http_delay_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_delay_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_delay_loc_conf_t));
if (conf == NULL) {
return NULL;
}
conf->delay = NGX_CONF_UNSET_MSEC;
return conf;
}
/* 合并 location 配置 */
static char *
ngx_http_delay_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_delay_loc_conf_t *prev = parent;
ngx_http_delay_loc_conf_t *conf = child;
ngx_conf_merge_msec_value(conf->delay, prev->delay, 1000);
return NGX_CONF_OK;
}
要点说明:
-
delay指令仅允许在location块内出现(NGX_HTTP_LOC_CONF),接收一个时间参数。 -
ngx_parse_time()可以解析5000、5s、5000ms等形式,统一转为毫秒存入delay。 -
合并配置时默认延迟为 1000 毫秒(1 秒),若用户未指定
delay则使用该默认值。
8.3 nginx.conf 配置示例
动态编译:先通过 --add-dynamic-module 将模块编译为 .so,再在 nginx.conf 中用 load_module 加载:
load_module modules/ngx_http_delay_module.so;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name localhost;
location /delay {
delay 5000; # 延迟 5 秒后返回响应
}
}
}
静态编译:在 Nginx 源码目录执行 ./configure --add-module=/path/to/ngx_http_delay_module,然后 make && make install。此时 nginx.conf 中无需 load_module,直接使用 delay 指令即可。
编译完成后,启动 Nginx,访问 http://localhost/delay,大约 5 秒后即可看到 “Delay response after …” 的响应内容,证明你的事件驱动模块已经可以正常工作了。
8. 总结
通过本文的源码剖析和实战示例,你应该已经掌握:
-
Nginx 事件模块的三大核心数据结构:
ngx_event_t、ngx_connection_t及定时红黑树。 - I/O 读写事件的注册、回调处理与水平触发下的**“写即删”**策略。
-
利用
ngx_add_timer/ngx_del_timer精确控制超时,并在回调中通过timedout标志做出响应。 - 如何将这些知识点组合成一个完整的 Nginx 事件驱动模块。
事件模型是 Nginx 高性能的根基,理解并善用它将让你的 C 模块开发水平迈上一个新台阶。建议你在实际开发中多翻阅 src/event/ 和 src/http/ 下的源码,结合本文的脉络,很快就能写出生产级别的事件处理代码。













