Nginx读写事件触发与回调由自身单线程主动调度:EPOLLIN在监听套接字初始化及accept后注册,handler为ngx_event_accept或ngx_http_wait_request_handler;EPOLLOUT按需动态注册于发送阻塞时,handler为ngx_http_writer;epoll_wait仅搬运就绪事件至队列,主循环依次同步调用handler,全程非阻塞、无并发、上下文通过data字段传递。

读写事件的触发与回调处理,不是内核直接调用你的函数,而是 Nginx 自己在单线程中主动拉取、校验、再顺序调用 handler —— 整个过程同步、可控、无并发。
读事件怎么被触发和处理
读事件(EPOLLIN)触发的前提是 socket 缓冲区有数据可读,但 Nginx 并不依赖内核“通知即执行”,而是分三步走:
- 监听套接字在 worker 初始化时注册 EPOLLIN,handler 设为 ngx_event_accept;新连接 accept 后,立即为该客户端 socket 注册读事件,handler 通常设为 ngx_http_wait_request_handler 或 ngx_http_process_request_line
- epoll_wait 返回就绪事件后,Nginx 把对应 ngx_event_t * 填入就绪队列,不立刻读数据,只排队
- 主循环遍历就绪队列,对每个有效读事件调用 ev->handler(ev);handler 内部用非阻塞 recv 循环读取,直到返回 EAGAIN,确保 ET 模式下不丢数据
写事件怎么被触发和处理
写事件(EPOLLOUT)不是默认监听的,它只在真正需要发送数据且系统缓冲区满时才动态注册:
- HTTP 响应生成后,若 send() 返回 EAGAIN,说明内核缓冲区已满,此时调用 ngx_handle_write_event() 注册 EPOLLOUT,并把 handler 设为 ngx_http_writer
- 下次 epoll_wait 就绪后,该写事件进入队列,主循环调用其 handler;handler 尝试继续发送,若仍不能全发完,保持 EPOLLOUT 注册状态;若发完,则主动取消注册(避免空转触发)
- 长连接或上游代理场景中,写事件可能多次注册/注销,完全按需,不常驻
handler 执行的关键约束
所有读写 handler 都运行在 worker 主线程中,必须满足三个硬性条件:
- 不能阻塞:socket 全局设为 O_NONBLOCK,recv/send 必须配合 EAGAIN 处理,禁止 sleep、阻塞 read 等操作
- 不能长时间占用:一个 handler 执行太久会卡住整个 worker;Nginx 通过分阶段(如 request line → headers → body)拆解 HTTP 处理,每阶段只做少量工作
- 上下文靠 data 字段传递:每个 ngx_event_t 的 data 指向 ngx_connection_t,而 connection 的 data 字段又常挂 ngx_http_request_t,形成清晰的生命周期链路
为什么不用操作系统回调
Nginx 放弃内核级回调(比如 epoll 不支持用户函数指针),选择自己调度,是为了掌控力:
- 避免中断上下文带来的栈限制与调试困难
- 统一管理定时器、清理事件、stale event 过滤,全部纳入同一遍历流程
- 便于做连接池复用、内存预分配、零拷贝优化等深度控制



















