nginx采用内核事件通知机制实现高效io,基于epoll/kqueue,通过“等通知—分发—非阻塞处理—重注册”四步闭环调度,结合et模式与reactor架构完成单进程高并发处理。

Nginx 的 IO 事件不是靠轮询或开多线程“硬扛”,而是由内核主动通知、单 Worker 进程内闭环调度完成的。整个流程围绕“等通知—分发—非阻塞处理—重注册”四步展开,关键在精准控制每个 socket 的监听状态,避免漏事件或空转。
事件如何被内核感知并推送给 Nginx
Nginx 启动时自动选用最优机制:Linux 用 epoll,FreeBSD/macOS 用 kqueue。它们让内核替 Nginx 盯住一批文件描述符(如监听 socket、客户端连接),一旦有数据可读、可写、新连接到达或异常发生,就立刻“推”事件,而不是让 Nginx 反复查询。
- epoll 使用红黑树管理注册的 fd,插入/删除复杂度为 O(logN),就绪事件通过链表直接返回,无需遍历全部 fd
- kqueue 支持更广事件类型(如文件变更、子进程退出),且 kevent 调用支持批量提交与获取
- Nginx 默认启用边缘触发(ET)模式,要求应用层必须一次性读完或写完,否则可能漏后续通知
事件从就绪到执行的三步调度链路
当 epoll_wait 或 kevent 返回就绪事件后,Nginx 并不直接执行业务逻辑,而是走标准 Reactor 模式:
- 事件分发:遍历就绪列表,按 fd 类型(监听 socket / 已连接 socket)和事件类型(EPOLLIN / EPOLLOUT / EPOLLERR)路由到对应 handler
- 非阻塞处理:对读事件,循环调用 recv() 直到返回 EAGAIN;对写事件,只发当前缓冲区能容纳的数据,未发完则保留 EPOLLOUT 注册等待下一次通知
- 状态重注册:读完请求头后若还需读请求体,保持 EPOLLIN;若要发响应,则改注册 EPOLLOUT;连接关闭则从事件表彻底移除
事件注册的时机与方式
Nginx 不在 socket 创建时立即注册,而是在真正需要监听时才调用 ngx_epoll_add_event():
- 监听 socket 在初始化阶段通过 ngx_add_listen() 注册 EPOLLIN
- 新 accept 的连接,在 ngx_event_accept() 后调用 ngx_add_conn() 注册读事件(EPOLLIN)或读写事件(如长连接需即时响应)
- 主动发起写操作前(如定时器触发、上游连接建立后),可能注册 EPOLLOUT,并带上 EPOLLET 标志
- 注册本质是 epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ev),其中 ev 封装了事件类型、数据指针(指向 ngx_event_t*)及触发模式
worker 中的核心处理函数:ngx_epoll_process_events
这是 worker 循环里实际干活的函数,流程清晰:
- 调用 epoll_wait(ep, event_list, nevents, timer),阻塞等待事件,timer 来自 ngx_event_find_timer(),实现超时控制
- 遍历每个就绪事件,从 ev->data.ptr 恢复原始 ngx_event_t* 指针
- 根据 revents 字段判断类型:EPOLLIN 触发 rev->handler(如 ngx_http_wait_request_handler),EPOLLOUT 触发 wev->handler
- 启用 ET 模式时,若 recv() 未读完就返回 EAGAIN,不会重新添加事件——依赖内核 level-triggered fallback 或用户保证非阻塞行为











