nginx 必须将所有文件句柄设为 o_nonblock,并配合 epoll/kqueue 等异步 i/o 机制,统一管理事件注册与错误处理(eagain/ewouldblock),严禁直接 close(),确保单进程高效处理高并发。

Nginx 在 Non-blocking(非阻塞)模式下操作文件句柄,核心是避免 I/O 等待阻塞事件循环,确保单进程能高效处理成千上万并发连接。它不依赖多线程或多进程抢占式调度,而是靠 epoll/kqueue/iocp 等异步 I/O 机制配合非阻塞文件描述符(file descriptor, fd)实现高吞吐。规范性操作不是“可选技巧”,而是 Nginx 正常运行的底层前提。
文件句柄必须设为 O_NONBLOCK
Nginx 在 open() 或 accept() 获取新 fd 后,会立即调用 fcntl(fd, F_SETFL, O_NONBLOCK) 将其置为非阻塞模式。这是硬性要求:
- 所有由 Nginx 主动打开的文件(如静态资源、日志、SSL 证书)都需显式设置 O_NONBLOCK;
- accept() 接收的客户端 socket 默认继承监听 socket 的非阻塞属性(前提是监听 socket 已设为非阻塞);
- 若上游模块(如 proxy_pass)或第三方模块绕过 Nginx 标准 API 直接 open() 文件,未设 O_NONBLOCK,read()/write() 可能意外阻塞 worker 进程,导致整个事件循环卡顿。
读写必须适配 EAGAIN/EWOULDBLOCK 错误码
非阻塞 fd 的 read()/write() 调用不会等待数据就绪,而是在无数据可读或内核发送缓冲区满时立即返回 -1,并置 errno 为 EAGAIN 或 EWOULDBLOCK(Linux 下二者等价)。Nginx 的标准处理逻辑是:
- read() 返回 EAGAIN:表示当前无数据,但连接仍有效,应注册 EPOLLIN 事件,等待下次可读通知;
- write() 返回 EAGAIN:表示内核发送缓冲区已满,需暂停发送,注册 EPOLLOUT 事件,待缓冲区有空闲再重试;
- 绝对不可忽略该错误并重试(busy-loop),也不可直接关闭连接——这属于正常流控信号,不是异常。
文件句柄生命周期由 Nginx 事件框架统一管理
Nginx 不允许模块自行 close() 一个正在被事件驱动层监控的 fd。正确流程是:
- 通过 ngx_close_connection() 或 ngx_http_finalize_request() 等标准接口触发关闭流程;
- 该流程会先从 epoll/kqueue 中删除事件监听(del_event),再执行 close();
- 若模块在 handler 中直接 close(fd),可能导致后续事件回调访问已释放 fd,引发段错误或静默失败;
- 对于长连接(keepalive),fd 不会立即关闭,而是复用并重新注册读事件,此时仍需保持 O_NONBLOCK 属性不变。
日志与临时文件也需非阻塞语义兼容
虽然磁盘 I/O 无法真正异步(Linux 原生 aio 限制多),Nginx 对日志和临时文件(如 client_body_temp)仍遵循非阻塞原则:
- 日志写入采用 buffered write + 定时刷盘(或 size 触发),避免每次 write() 阻塞;
- client_body_in_file_only 开启后,上传体写入临时文件使用非阻塞方式打开(O_NONBLOCK + O_CREAT | O_WRONLY),但实际 write() 仍可能因磁盘慢而短暂阻塞 —— 因此生产环境建议将 temp_path 置于高速存储,并控制 client_max_body_size;
- 所有文件操作均通过 ngx_open_file、ngx_write_fd 等封装函数,这些函数内部已集成非阻塞容错逻辑。
不复杂但容易忽略:非阻塞不是开关,而是一整套协同机制。从 fd 创建、事件注册、错误处理到资源释放,每个环节都必须与 Nginx 的 event loop 严格对齐。










