当前位置: 首页 > news >正文

ngx_http_init_connection

1 定义

ngx_http_init_connection 函数 定义在 ./nginx-1.24.0/src/http/ngx_http_request.c
voidngx_http_init_connection(ngx_connection_t*c){ngx_uint_ti;ngx_event_t*rev;structsockaddr_in*sin;ngx_http_port_t*port;ngx_http_in_addr_t*addr;ngx_http_log_ctx_t*ctx;ngx_http_connection_t*hc;ngx_http_core_srv_conf_t*cscf;#if(NGX_HAVE_INET6)structsockaddr_in6*sin6;ngx_http_in6_addr_t*addr6;#endifhc=ngx_pcalloc(c->pool,sizeof(ngx_http_connection_t));if(hc==NULL){ngx_http_close_connection(c);return;}c->data=hc;/* find the server configuration for the address:port */port=c->listening->servers;if(port->naddrs>1){/* * there are several addresses on this port and one of them * is an "*:port" wildcard so getsockname() in ngx_http_server_addr() * is required to determine a server address */if(ngx_connection_local_sockaddr(c,NULL,0)!=NGX_OK){ngx_http_close_connection(c);return;}switch(c->local_sockaddr->sa_family){#if(NGX_HAVE_INET6)caseAF_INET6:sin6=(structsockaddr_in6*)c->local_sockaddr;addr6=port->addrs;/* the last address is "*" */for(i=0;i<port->naddrs-1;i++){if(ngx_memcmp(&addr6[i].addr6,&sin6->sin6_addr,16)==0){break;}}hc->addr_conf=&addr6[i].conf;break;#endifdefault:/* AF_INET */sin=(structsockaddr_in*)c->local_sockaddr;addr=port->addrs;/* the last address is "*" */for(i=0;i<port->naddrs-1;i++){if(addr[i].addr==sin->sin_addr.s_addr){break;}}hc->addr_conf=&addr[i].conf;break;}}else{switch(c->local_sockaddr->sa_family){#if(NGX_HAVE_INET6)caseAF_INET6:addr6=port->addrs;hc->addr_conf=&addr6[0].conf;break;#endifdefault:/* AF_INET */addr=port->addrs;hc->addr_conf=&addr[0].conf;break;}}/* the default server configuration for the address:port */hc->conf_ctx=hc->addr_conf->default_server->ctx;ctx=ngx_palloc(c->pool,sizeof(ngx_http_log_ctx_t));if(ctx==NULL){ngx_http_close_connection(c);return;}ctx->connection=c;ctx->request=NULL;ctx->current_request=NULL;c->log->connection=c->number;c->log->handler=ngx_http_log_error;c->log->data=ctx;c->log->action="waiting for request";c->log_error=NGX_ERROR_INFO;rev=c->read;rev->handler=ngx_http_wait_request_handler;c->write->handler=ngx_http_empty_handler;#if(NGX_HTTP_V2)if(hc->addr_conf->http2){rev->handler=ngx_http_v2_init;}#endif#if(NGX_HTTP_SSL){ngx_http_ssl_srv_conf_t*sscf;sscf=ngx_http_get_module_srv_conf(hc->conf_ctx,ngx_http_ssl_module);if(sscf->enable||hc->addr_conf->ssl){hc->ssl=1;c->log->action="SSL handshaking";rev->handler=ngx_http_ssl_handshake;}}#endifif(hc->addr_conf->proxy_protocol){hc->proxy_protocol=1;c->log->action="reading PROXY protocol";}if(rev->ready){/* the deferred accept(), iocp */if(ngx_use_accept_mutex){ngx_post_event(rev,&ngx_posted_events);return;}rev->handler(rev);return;}cscf=ngx_http_get_module_srv_conf(hc->conf_ctx,ngx_http_core_module);ngx_add_timer(rev,cscf->client_header_timeout);ngx_reusable_connection(c,1);if(ngx_handle_read_event(rev,0)!=NGX_OK){ngx_http_close_connection(c);return;}}
`ngx_http_init_connection` 函数的作用是: 为刚建立的 TCP 连接初始化 HTTP 层处理环境。 它分配连接专属的 HTTP 上下文, 根据本地 IP 与端口匹配对应的 server 配置块,设置日志、读/写事件回调 添加客户端请求超时,并将读事件注册到底层事件驱动模块, 使连接正式进入等待接收客户端请求的状态。

2 详解

1 函数签名

voidngx_http_init_connection(ngx_connection_t*c)
无返回值
参数 ngx_connection_t *c 指向要初始化的连接对象

2 逻辑流程

1 局部变量 2 分配并绑定 HTTP 连接上下文 3 根据本地地址匹配 server 配置 4 选定默认 server 配置 5 初始化 HTTP 日志上下文 6 设置事件回调 7 处理数据已就绪的情形 8 设置超时并注册读事件

1 局部变量
{ngx_uint_ti;ngx_event_t*rev;structsockaddr_in*sin;ngx_http_port_t*port;ngx_http_in_addr_t*addr;ngx_http_log_ctx_t*ctx;ngx_http_connection_t*hc;ngx_http_core_srv_conf_t*cscf;#if(NGX_HAVE_INET6)structsockaddr_in6*sin6;ngx_http_in6_addr_t*addr6;#endif

2 分配并绑定 HTTP 连接上下文
hc=ngx_pcalloc(c->pool,sizeof(ngx_http_connection_t));if(hc==NULL){ngx_http_close_connection(c);return;}c->data=hc;
ngx_pcalloc: 从连接内存池 c->pool 分配内存并清零。 分配失败处理: 如果内存不足,直接关闭连接并返回。 c->data = hc: 将 HTTP 私有数据挂载到通用连接对象上。 此后无论在哪个模块,只要拿到 c,就可以通过 hc = c->data 取回 HTTP 层专有信息 这是 nginx 实现模块扩展的典型方式:通用结构体里留一个 void* data 指针。

3 根据本地地址匹配 server 配置
/* find the server configuration for the address:port */port=c->listening->servers;if(port->naddrs>1){/* * there are several addresses on this port and one of them * is an "*:port" wildcard so getsockname() in ngx_http_server_addr() * is required to determine a server address */if(ngx_connection_local_sockaddr(c,NULL,0)!=NGX_OK){ngx_http_close_connection(c);return;}switch(c->local_sockaddr->sa_family){#if(NGX_HAVE_INET6)caseAF_INET6:sin6=(structsockaddr_in6*)c->local_sockaddr;addr6=port->addrs;/* the last address is "*" */for(i=0;i<port->naddrs-1;i++){if(ngx_memcmp(&addr6[i].addr6,&sin6->sin6_addr,16)==0){break;}}hc->addr_conf=&addr6[i].conf;break;#endifdefault:/* AF_INET */sin=(structsockaddr_in*)c->local_sockaddr;addr=port->addrs;/* the last address is "*" */for(i=0;i<port->naddrs-1;i++){if(addr[i].addr==sin->sin_addr.s_addr){break;}}hc->addr_conf=&addr[i].conf;break;}}else{switch(c->local_sockaddr->sa_family){#if(NGX_HAVE_INET6)caseAF_INET6:addr6=port->addrs;hc->addr_conf=&addr6[0].conf;break;#endifdefault:/* AF_INET */addr=port->addrs;hc->addr_conf=&addr[0].conf;break;}}
#1 c->listening 是本次 accept 所在的监听对象, 它的 servers 字段指向 ngx_http_port_t, 其中包含了该端口上所有 listen 指令合并后的地址信息。 这是连接与具体 server 配置关联的入口。
#2-1 情况1:多地址(port->naddrs > 1) port->naddrs > 1:表示该端口绑定了多个不同的 IP 地址
#2-2 调用 ngx_connection_local_sockaddr ngx_connection_local_sockaddr 作用是 获取并缓存连接的本端 socket 地址 (即 accept 产生的 socket 实际绑定的本地 IP 和端口)。 它内部会调用 getsockname() 系统调用。 第一个参数 c: 当前的连接对象。 第二个参数 NULL: 表示我们不关心对端地址 (通常此函数也可用于同时获取对端地址,这里传 NULL 忽略)。 第三个参数 0: 标志位,通常为 0 表示默认行为。 返回值: 成功时返回 NGX_OK,失败返回 NGX_ERROR。 != NGX_OK: 条件判断如果函数未返回成功, 说明获取本地地址失败,进入 if 体进行错误处理。 意义: 当 port->naddrs > 1(端口上配置了多个监听地址), 不同的本地 IP 可能对应不同的虚拟主机配置 因此必须精确知道客户端究竟连接到了哪个 IP。 这个信息只能通过 getsockname() 获取, 并且函数会将其保存到 c->local_sockaddr 中, 供后续地址匹配循环使用。 != NGX_OK: 失败 调用 HTTP 模块的连接关闭函数 ngx_http_close_connection 该函数会执行一系列清理工作
#2-3 根据连接的本地 IP 协议族, 在端口的多地址数组中找到匹配的那一项, 从而为连接选定正确的地址级配置 c->local_sockaddr: 此前已通过 ngx_connection_local_sockaddr 填充, 保存了本端(服务器)的 socket 地址。 sa_family: socket 地址结构体中的协议族字段, 用于区分 IPv4(AF_INET)或 IPv6(AF_INET6)。 switch: 根据协议族分支处理, 因为 IPv4 和 IPv6 的地址数据结构和长度不同, 必须用不同的代码匹配。
#2-3-1 当连接本地地址协议族为 AF_INET6 时 将通用的 sockaddr 指针强制转换为 sockaddr_in6 指针, 以便访问 IPv6 地址字段 sin6_addr port->addrs: 在 IPv6 场景下,这是 ngx_http_in6_addr_t 数组, 每个元素代表该端口上监听的一个 IPv6 地址及其配置。 addr6:局部指针,方便后续遍历。 循环遍历数组的前 naddrs - 1 个元素(即除了最后一个通配符以外的所有具体地址)。 意义:精确匹配具体 IP, 保证在多个具体地址共存时找到正确的那一个; 若未匹配,循环结束后 i 自动等于 naddrs - 1,即通配符索引。 addr6[i].addr6:数组中某个监听地址的 IPv6 地址(struct in6_addr)。 sin6->sin6_addr:本端 socket 的实际 IPv6 地址。 ngx_memcmp:按字节比较两块内存,第三个参数 16 即 IPv6 地址长度(128 位/16 字节)。 == 0:相等说明该连接实际到达的本地 IP 就是这个监听地址。 一旦找到匹配的地址,立即跳出循环。此时 i 就是匹配项的索引 将匹配到的那一项地址配置块(conf 字段)的地址赋给 hc->addr_conf。
#2-3-2 当协议族不是 AF_INET6 时,按 IPv4 处理 转换为 sockaddr_in 指针,准备提取 IPv4 地址 addr = port->addrs; 指向 IPv4 的地址配置数组 遍历除通配符外的所有具体 IPv4 地址. addr[i].addr: 监听地址的 IPv4 地址(以 in_addr_t 整型存储)。 sin->sin_addr.s_addr: 本端 socket 的实际 IPv4 地址(同样为整型)。 直接进行整型相等比较,比 memcmp 更简单高效。 匹配成功跳出循环 hc->addr_conf = &addr[i].conf; 赋值地址配置,意义同 IPv6 分支。
#3-1 情况2:单地址(port->naddrs == 1) 当端口只有一个监听地址(可能是具体 IP 也可能是 *), 不需要通过地址查找,直接取 addrs[0].conf 即可。 这里依然根据协议族区分,

4 选定默认 server 配置
/* the default server configuration for the address:port */hc->conf_ctx=hc->addr_conf->default_server->ctx;
addr_conf 是地址级别的配置, 其内部有一个 default_server 指针, 指向该监听地址上配置的默认虚拟主机(server 块)。 default_server 由 listen 指令的 default_server 参数指定, 若无则为第一个 server。 ctx 是该 server 块的配置上下文, 它保存了所有 HTTP 模块在 server 级别产生的配置结构 通过 ngx_http_get_module_srv_conf 能从中取出所需配置。 为什么此时就要选定默认 server? 因为客户端请求尚未到达,我们还不知道 Host 头会是什么,无法确定最终处理请求的 server。 但初始阶段已有一些工作需要 server 级配置信息 (例如读取请求头时的超时时间 client_header_timeout,以及是否需要 SSL 握手)。 因此先临时使用默认 server 的配置上下文,后续解析请求头后, 若 Host 指向另一个 server,会在 ngx_http_process_request 的阶段重新进行 server 跳转。

5 初始化 HTTP 日志上下文
ctx=ngx_palloc(c->pool,sizeof(ngx_http_log_ctx_t));if(ctx==NULL){ngx_http_close_connection(c);return;}ctx->connection=c;ctx->request=NULL;ctx->current_request=NULL;c->log->connection=c->number;c->log->handler=ngx_http_log_error;c->log->data=ctx;c->log->action="waiting for request";c->log_error=NGX_ERROR_INFO;

6 设置事件回调
6-1 设置默认事件回调
rev=c->read;rev->handler=ngx_http_wait_request_handler;c->write->handler=ngx_http_empty_handler;
rev = c->read: 取得读事件对象,此后反复使用。 rev->handler = ngx_http_wait_request_handler: 将读事件回调设为等待客户端 HTTP 请求头的函数。 这是标准 HTTP/1.x 的起点,该函数会读取数据,解析请求行和头,并在完成后进入请求处理流程。 c->write->handler = ngx_http_empty_handler: 写事件回调先设为一个空函数(只打印调试日志), 因为此时没有任何响应数据需要发送, 写事件不应该被触发;如果意外触发,空处理安全无害。

6-2 协议前置处理:HTTP/2
#if(NGX_HTTP_V2)if(hc->addr_conf->http2){rev->handler=ngx_http_v2_init;}#endif
如果编译时包含了 HTTP/2 模块, 并且该监听地址配置了 http2 选项, 则直接覆盖读事件回调为 ngx_http_v2_init。 ngx_http_v2_init 负责处理 HTTP/2 的连接前言(preface)和 SETTINGS 帧, 将连接升级到 HTTP/2 模式。这意味着不再走 HTTP/1.x 的请求头读取路径。

6-3 协议前置处理:SSL/TLS
#if(NGX_HTTP_SSL){ngx_http_ssl_srv_conf_t*sscf;sscf=ngx_http_get_module_srv_conf(hc->conf_ctx,ngx_http_ssl_module);if(sscf->enable||hc->addr_conf->ssl){hc->ssl=1;c->log->action="SSL handshaking";rev->handler=ngx_http_ssl_handshake;}}#endif
获取 SSL server 配置: 判断 SSL 是否启用:两个条件满足其一即可: sscf->enable:在 server 块里显示写了 ssl on; hc->addr_conf->ssl:在 listen 指令中直接写了 ssl 参数(如 listen 443 ssl;)。 如果启用 SSL: hc->ssl = 1:标记此连接为 SSL 连接,后续各阶段可据此做特殊处理。 更新 action 为 "SSL handshaking",以便日志反映当前正在 SSL 握手。 将读事件回调替换为 ngx_http_ssl_handshake: 这个函数会接管连接,进行 SSL/TLS 握手。握手完成后, 它会根据是否协商出 HTTP/2 等进一步设置最终的请求处理回调 这里存在一个明显的优先级: 如果 HTTP/2 和 SSL 都开启,由于 SSL 代码在后面,rev->handler 最终会被设为 ngx_http_ssl_handshake, 即先进行 SSL 握手,握手成功后再由 SSL 内部逻辑触发 HTTP/2 的初始化。 这是正确的顺序,因为 HTTP/2 over TLS 必须先完成 TLS 握手。

6-4 协议前置处理:PROXY 协议
if(hc->addr_conf->proxy_protocol){hc->proxy_protocol=1;c->log->action="reading PROXY protocol";}
PROXY 协议用于在代理场景下传递真实客户端 IP。 如果该监听地址开启了 proxy_protocol 选项, 则先在 HTTP 上下文标记 proxy_protocol = 1, 并更新 action。

7 处理数据已就绪的情形
if(rev->ready){/* the deferred accept(), iocp */if(ngx_use_accept_mutex){ngx_post_event(rev,&ngx_posted_events);return;}rev->handler(rev);return;}
rev->ready: 如果在注册事件之前,内核已经将数据放在了 socket 接收缓冲区 (例如使用了 TCP_DEFER_ACCEPT 或在 Windows IOCP 下), 那么此时读事件已经就绪,无需再等待 epoll/kqueue 通知。 使用 accept 互斥锁时的处理: ngx_post_event(rev, &ngx_posted_events) 将事件放入 ngx_posted_events 延迟队列, 等互斥锁释放后再批量执行。 这是为了避免在持有锁期间执行复杂的请求处理逻辑导致其他 worker 长时间阻塞。 未使用互斥锁时: 直接调用当前设置好的 handler(可能是 SSL 握手、HTTP/2 初始化或等待请求),立即开始处理数据。 无论哪种情况,直接 return, 不再执行后面的定时器和事件注册,因为事件已经是就绪状态,不需要额外触发。

8 设置超时并注册读事件
cscf=ngx_http_get_module_srv_conf(hc->conf_ctx,ngx_http_core_module);ngx_add_timer(rev,cscf->client_header_timeout);ngx_reusable_connection(c,1);if(ngx_handle_read_event(rev,0)!=NGX_OK){ngx_http_close_connection(c);return;}}
获取核心配置: cscf 是默认 server 的 ngx_http_core_srv_conf_t, 其中包含 client_header_timeout(默认为 60s), 表示等待客户端发送完整请求头的最大时间。 添加定时器: ngx_add_timer(rev, cscf->client_header_timeout) 将读事件挂到红黑树定时器上, 超时后会调用读事件的 handler ngx_reusable_connection(c, 1): 标记连接为“可重用” 将连接设置为可复用,是为了在连接资源即将耗尽时,将虽然还没有超时, 但空闲时间最长的连接回收用来接受新的客户端连接 注册读事件: ngx_handle_read_event(rev, 0) 将读事件正式添加到事件驱动机制(epoll/kqueue 等)中, 开始监听客户端数据。 flags=0 表示普通模式,如果失败则关闭连接。
http://www.jsqmd.com/news/769350/

相关文章:

  • 2026年第二季度国内化工流量计厂家深度解析与选型指南 - 流量计品牌
  • 进口真空烘箱/智能烘箱哪个厂家品质好 实力派制造企业榜单 - 品牌推荐大师1
  • 2026年新疆三元催化器专业公司推荐榜TOP5 - 速递信息
  • 别再为抓不到FPGA信号发愁了!手把手教你用Vivado的VIO IP核做精准调试
  • 告别速度模糊:手把手教你用TI AWR2944的DDMA波形提升毫米波雷达性能
  • 观察大流量并发请求下API聚合服务的稳定性表现
  • CCAA补考政策是什么? - 众智商学院官方
  • 【云藏山鹰代数信息系统】浅析意气实体过程知识图谱12
  • 娱乐圈天降紫微星终现真身,海棠山铁哥不靠人间资源靠天道
  • 大学生备考CFA|揽星CFA APP零成本助力,课业备考双兼顾不内耗 - 速递信息
  • 轻量级网络节点推送工具:Go语言实现的自托管消息推送服务
  • Honey Select 2终极汉化补丁:3步告别日语障碍,畅享中文游戏体验
  • 2026珠三角企业团建复购率排行:5家客户粘性高的服务商,含满意度95%、80%复购率、全周期复盘报告等 - 速递信息
  • 陪同翻译推荐公司有哪些?北京这家口译公司口碑稳、性价比高 - 品牌推荐大师1
  • FastMCP 服务说明文档
  • 语音打断、流式播报、前置指令:打造工业级AI语音交互体验
  • Coolapk-UWP:重新定义Windows桌面上的酷安社区体验
  • Swin Transformer注意力计算复杂度为何比全局注意力少那么多?
  • BiliDownload:3分钟掌握B站视频下载的终极免费方案
  • TVA与CNN的历史性对决(19)
  • AISMM认证全流程时间轴:22个工作日压缩至11天的实战策略(含SITS2026独家加急通道申请模板)
  • 微信立减金回收怎么操作最安全靠谱?避开骗局快速变现 - 米米收
  • 3步掌握MTK设备救砖:从黑屏到正常启动的完整指南
  • 国产AI模型平台崛起:模力方舟如何破解HuggingFace本土化困境
  • 别再死记硬背了!用Vivado手把手教你配置RFSoC的ADC混频器(Fine/IQ模式详解)
  • 别再死磕OPC DA了!手把手教你用OPC UA搞定跨平台工业数据采集(附Python示例)
  • Python开发在数据分析领域的应用探索
  • 使用 Taotoken 后 API 调用成功率与延迟的直观观测体验
  • Pearcleaner:macOS终极清理工具,5个独特功能让您的Mac焕然一新
  • 3分钟极简配置:Onekey自动化工具如何重塑Steam游戏清单管理体验