ngx_connection_t是worker进程内预分配、可复用的连接槽位,生命周期由连接池统一管理,不随socket关闭而销毁,仅在worker退出时释放内存。

ngx_connection_t 不是“被创建后一直存在”的连接句柄,而是可复用的连接槽位;它的生命周期由 worker 进程内连接池统一管理,与 socket 状态、keepalive 设置、内存池释放强绑定。
ngx_connection_t 从哪来:连接池分配与复用机制
每个 worker 进程启动时就按 worker_connections 预分配固定数量的 ngx_connection_t 结构体,全部挂入空闲链表。新连接到来时,ngx_get_connection 从链表头部取一个,不 malloc;连接关闭后,ngx_free_connection 归还,不 free。
关键点:
- 它不是每次 accept 都 new 出来的,也不是每个请求独占一个
- 结构体内含
read和write两个ngx_event_t *字段,handler 指针在不同阶段会被反复覆盖(比如从ngx_event_accept切到ngx_http_init_request) - 若启用了 keepalive,连接关闭后不会立即归还,而是重置状态、清空 request pool、等待下一次 read ready,此时
ngx_connection_t仍被持有,但已脱离上一个请求上下文
ngx_connection_t 怎么绑定请求:r->connection 与 request pool 的关系
HTTP 请求开始时,ngx_http_init_request 把当前 ngx_connection_t * 赋给新建的 ngx_http_request_t *r 的 r->connection 字段,并为该请求分配专属的 r->pool。所有模块在处理这个请求时申请的内存(包括临时 buffer、ctx、header 引用等),都来自这个 pool。
这意味着:
-
ngx_connection_t是连接层载体,ngx_http_request_t是应用层载体,二者通过指针关联,但生命周期不同 - 一旦
ngx_http_finalize_request调用,会触发ngx_http_free_request→ 销毁r->pool→ 所有 request 级内存一次性释放,但ngx_connection_t可能还在(keepalive 场景) - 不能把指向
r->pool内存的指针保存在c->data或全局变量里——下次请求复用该 connection 时,那些指针早已失效
ngx_connection_t 何时真正销毁:连接关闭与资源回收边界
ngx_connection_t 本身不会被销毁,除非 worker 进程退出。它只会在两种情况下从活跃态转为空闲态:
- 显式 close:调用
ngx_close_connection(c),清理 socket、事件、timer,调用ngx_free_connection(c)归还至空闲链表 - 异常中断:如 read timeout、client abrupt disconnect、SSL handshake fail,最终也走
ngx_close_connection流程
注意:ngx_close_connection 不会释放 ngx_connection_t 内存,只是重置字段(如 c->read->handler = ngx_event_close_handler)、解除事件注册、关闭 fd,然后归还。真正的内存释放只发生在 worker 进程退出时。
容易踩的坑:handler、timer、data 字段的误用
很多模块错误地假设 c->data 或 c->read->handler 在整个连接生命周期内稳定可用,但实际中:
-
c->data通常指向ngx_http_request_t *,但在 keepalive 等待期间会被置为 NULL;子请求或 upstream 场景下也可能被临时覆盖 -
c->read->handler在不同阶段被多次重设:accept 后是ngx_http_wait_request_handler,收到完整 header 后变成ngx_http_process_request_line,后续可能又切到ngx_http_request_body_handler - 定时器(如
ngx_add_timer(c->read, c->read_timeout))必须确保回调函数访问的数据仍在有效内存中;若绑定到 request pool,必须在 request 销毁前显式删除 timer,否则回调执行时会访问已释放内存
最常被忽略的一点:ngx_connection_t 复用不等于上下文复用——你看到的是同一个结构体地址,但里面绝大多数字段(尤其是 data、read/write->handler、buffer)都已被重置或重新赋值,不能靠“上次用过”做任何假设。



















