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

ngx_event_accept

1 定义

ngx_event_accept 函数 定义在 ./nginx-1.24.0/src/event/ngx_event_accept.c

2 作用

ngx_event_accept 是 Nginx 中处理新连接到达的核心函数。 它从监听套接字上 accept 新的客户端连接, 为其创建并初始化 `ngx_connection_t` 结构体, 然后调用监听端口配置的 handler 将连接交给上层协议(如 HTTP)处理。

3 详解

1 函数签名

voidngx_event_accept(ngx_event_t*ev)
1. 返回值类型 `void` 含义: 函数不返回任何值,调用者无法获得操作成功或失败的直接反馈。 `ngx_event_accept` 是一个 事件处理器(event handler), 由事件框架在监听套接字上有新连接到达时回调。 它的职责是 “尽可能地接受并初始化新连接”, 然后将连接交给上层协议(如 HTTP)处理,而不需要向事件分发器汇报结果。 函数内部遇到的所有错误 都会被当场处理: 记录日志、暂时禁用监听事件、释放资源等。 上层事件循环无需知道每次 `accept` 的细节,只需继续等待下一次事件。
2. 函数名 `ngx_event_accept` 命名空间前缀 `ngx_`:Nginx 函数的标准前缀。 `event_accept`: 准确描述了函数的用途 —— 处理监听套接字上的 “可接受新连接” 事件。 `event`:表明这是一个事件处理函数。 `accept`:直接对应 TCP 的 `accept` 系统调用,表示接收新连接。 整体语义: 该函数是 Nginx 中所有监听端口上新连接到达的统一入口。 无论是 HTTP、HTTPS、Stream 还是 Mail, 当监听 socket 上有新连接时, 最终都会调用此函数来执行 `accept` 并初始化连接。
3. 参数 `ngx_event_t *ev` 类型: 指向 `ngx_event_t` 结构体的指针, 这是 Nginx 事件系统的核心数据结构,代表一个 注册在事件驱动机制中的事件。
总结 `ngx_event_accept` 的函数签名是 Nginx 事件处理器接口的经典范例: - `void` 返回值 体现了回调函数的 “执行并遗忘” 特性,错误处理内聚在函数内部。 - 函数名直接揭示了它的业务含义:处理 TCP 连接的 accept 事件。 - 唯一的 `ev` 参数 作为事件对象的抽象,承载了触发上下文的所有信息, 使得框架能够以极简的接口驱动复杂的网络 I/O 处理。

2 逻辑流程

1 文件描述符耗尽时的延迟重试 2 设置一次性接受连接的数量 3 接受新连接 3-1 提取监听连接与监听配置,重置 ready 标志 3-2 循环持续接受新连接 3-2-1 调用 accept 获取新套接字 3-2-2 accept 失败 3-2-3 accept 成功

{socklen_tsocklen;ngx_err_terr;ngx_log_t*log;ngx_uint_tlevel;ngx_socket_ts;ngx_event_t*rev,*wev;ngx_sockaddr_tsa;ngx_listening_t*ls;ngx_connection_t*c,*lc;ngx_event_conf_t*ecf;#if(NGX_HAVE_ACCEPT4)staticngx_uint_tuse_accept4=1;#endif
局部变量

1 文件描述符耗尽时的延迟重试

if(ev->timedout){if(ngx_enable_accept_events((ngx_cycle_t*)ngx_cycle)!=NGX_OK){return;}ev->timedout=0;}
当服务器因为文件描述符耗尽而暂时无法接受新连接时, 通过定时器延迟重试,并在重试时恢复监听。
`if (ev->timedout) {` `timedout` 是一个标志位 当一个事件被触发不是因为它所等待的 I/O 操作就绪, 而是因为一个关联的 定时器超时 了,Nginx 就会将该事件的 `timedout` 设置为 1。 什么时候会发生这种情况? 当 worker 进程的可用连接数(文件描述符)达到了上限(`worker_connections`), 它就无法再调用 `accept` 接受新连接。 此时,Nginx 会把这个监听事件从 epoll 等事件驱动模块中暂时移除(disable), 避免 epoll 持续报告可读事件,同时设置一个定时器。 当定时器到期后,事件模块会再次触发这个监听事件,并标记 `ev->timedout = 1`, 于是 `ngx_event_accept` 被调用 进入这个条件 调用 ngx_enable_accept_events 重新尝试恢复监听事件,以便继续接受新连接
`if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {` 它的作用是 重新将之前被移除的监听事件注册到事件驱动模块中。 返回值检查: 该函数返回 `NGX_OK` 表示重新启用监听事件成功;否则表示失败。 如果失败,意味着无法恢复监听,那么继续执行 `accept` 也没有意义, 所以直接 `return`,退出当前事件处理。
`ev->timedout = 0;` 成功重新启用监听事件后,将 `ev->timedout` 标志 清零 这样,下次该事件被触发时 `timedout` 已经恢复为初始状态(0), 避免错误地再次进入这个超时处理分支。
总结 这段代码实现了 Nginx 在 文件描述符耗尽 时的一种 延迟重试 机制: 1. 当连接数满: worker 进程无法 `accept`,它不会忙等,而是主动移除了监听事件,并设置定时器。 2. 超时后重试: 定时器到期后,事件回调再次进入 `ngx_event_accept`, 但此时 `timedout=1`,表明这不是真正的新连接到达,而是一次 “请重新尝试监听” 的通知。 3. 恢复监听: 代码检查到超时标志,就调用 `ngx_enable_accept_events` 将监听事件重新加回 epoll。 如果恢复成功,则清除标志;如果恢复失败,则直接返回,等待后续可能的再次尝试。

2 设置一次性接受连接的数量

ecf=ngx_event_get_conf(ngx_cycle->conf_ctx,ngx_event_core_module);if(!(ngx_event_flags&NGX_USE_KQUEUE_EVENT)){ev->available=ecf->multi_accept;}
获取事件核心模块的配置
判断当前使用的事件模型是否为 Kqueue 在 Kqueue 模型中,监听事件返回时, 内核会直接告诉应用程序当前有多少个新连接已经就绪, 这个数值会被填充到 ev->available 中。 因此 Kqueue 下 ev->available 的值是由内核提供的准确数量,不应该被配置覆盖。 所以代码跳过了对它的赋值,直接保留内核给出的值。
设置 ev->available 的值 ecf->multi_accept 是 multi_accept 指令的值。 这是一个配置指令,可以设置为 on 或 off。 当 multi_accept off 时,该值为 0。 当 multi_accept on 时,该值为 1 ev->available 是 ngx_event_t 结构体中的一个整型字段。 在 accept 过程中,它作为一个循环控制变量使用: 函数末尾是一个 do ... while (ev->available) 循环, 只要 ev->available 的值不为 0,循环就会继续尝试 accept 更多的连接。

3 接受新连接

lc=ev->data;ls=lc->listening;ev->ready=0;
提取监听连接与监听配置,重置 ready 标志

ngx_log_debug2(NGX_LOG_DEBUG_EVENT,ev->log,0,"accept on %V, ready: %d",&ls->addr_text,ev->available);

do{socklen=sizeof(ngx_sockaddr_t);#if(NGX_HAVE_ACCEPT4)if(use_accept4){s=accept4(lc->fd,&sa.sockaddr,&socklen,SOCK_NONBLOCK);}else{s=accept(lc->fd,&sa.sockaddr,&socklen);}#elses=accept(lc->fd,&sa.sockaddr,&socklen);#endif
调用 accept 获取新套接字

处理 accept 失败
if(s==(ngx_socket_t)-1){err=ngx_socket_errno;if(err==NGX_EAGAIN){ngx_log_debug0(NGX_LOG_DEBUG_EVENT,ev->log,err,"accept() not ready");return;}level=NGX_LOG_ALERT;if(err==NGX_ECONNABORTED){level=NGX_LOG_ERR;}elseif(err==NGX_EMFILE||err==NGX_ENFILE){level=NGX_LOG_CRIT;}#if(NGX_HAVE_ACCEPT4)ngx_log_error(level,ev->log,err,use_accept4?"accept4() failed":"accept() failed");if(use_accept4&&err==NGX_ENOSYS){use_accept4=0;ngx_inherited_nonblocking=0;continue;}#elsengx_log_error(level,ev->log,err,"accept() failed");#endifif(err==NGX_ECONNABORTED){if(ngx_event_flags&NGX_USE_KQUEUE_EVENT){ev->available--;}if(ev->available){continue;}}if(err==NGX_EMFILE||err==NGX_ENFILE){if(ngx_disable_accept_events((ngx_cycle_t*)ngx_cycle,1)!=NGX_OK){return;}if(ngx_use_accept_mutex){if(ngx_accept_mutex_held){ngx_shmtx_unlock(&ngx_accept_mutex);ngx_accept_mutex_held=0;}ngx_accept_disabled=1;}else{ngx_add_timer(ev,ecf->accept_mutex_delay);}}return;}

accept 成功
#if(NGX_STAT_STUB)(void)ngx_atomic_fetch_add(ngx_stat_accepted,1);#endifngx_accept_disabled=ngx_cycle->connection_n/8-ngx_cycle->free_connection_n;
计算 ngx_accept_disabled: 若空闲连接池剩余数量低于总容量的 1/8,计算结果为正数,后续将触发接收节流机制。

c=ngx_get_connection(s,ev->log);if(c==NULL){if(ngx_close_socket(s)==-1){ngx_log_error(NGX_LOG_ALERT,ev->log,ngx_socket_errno,ngx_close_socket_n" failed");}return;}
为新连接分配连接对象

c->type=SOCK_STREAM;
设置连接类型 将连接的类型设置为 SOCK_STREAM,表示这是一个 TCP 流式连接。

#if(NGX_STAT_STUB)(void)ngx_atomic_fetch_add(ngx_stat_active,1);#endifc->pool=ngx_create_pool(ls->pool_size,ev->log);if(c->pool==NULL){ngx_close_accepted_connection(c);return;}
创建连接专属内存池

if(socklen>(socklen_t)sizeof(ngx_sockaddr_t)){socklen=sizeof(ngx_sockaddr_t);}c->sockaddr=ngx_palloc(c->pool,socklen);if(c->sockaddr==NULL){ngx_close_accepted_connection(c);return;}ngx_memcpy(c->sockaddr,&sa,socklen);
保存客户端地址

log=ngx_palloc(c->pool,sizeof(ngx_log_t));if(log==NULL){ngx_close_accepted_connection(c);return;}
分配日志对象

设置套接字的阻塞或非阻塞模式
/* set a blocking mode for iocp and non-blocking mode for others */if(ngx_inherited_nonblocking){if(ngx_event_flags&NGX_USE_IOCP_EVENT){if(ngx_blocking(s)==-1){ngx_log_error(NGX_LOG_ALERT,ev->log,ngx_socket_errno,ngx_blocking_n" failed");ngx_close_accepted_connection(c);return;}}}else{if(!(ngx_event_flags&NGX_USE_IOCP_EVENT)){if(ngx_nonblocking(s)==-1){ngx_log_error(NGX_LOG_ALERT,ev->log,ngx_socket_errno,ngx_nonblocking_n" failed");ngx_close_accepted_connection(c);return;}}}
ngx_inherited_nonblocking: 一个全局标志,指示新接受的套接字是否已经自动设置为非阻塞。 在 Linux 上,如果使用了 accept4 并成功,这个标志为 1;如果没有,则需要手动设置。 IOCP 特殊处理:IOCP(Windows 下的完成端口)需要套接字为阻塞模式, 所以如果当前事件模型是 IOCP,则调用 ngx_blocking 将套接字设为阻塞。 其他事件模型: 默认(epoll, kqueue 等)都要求非阻塞模式, 因此如果继承的非阻塞标志为 0(即需要手动设置), 且不是 IOCP 模型,则调用 ngx_nonblocking 设置 O_NONBLOCK 标志。 任何设置失败都会关闭连接并返回,确保后续不会在错误的阻塞模式下运行。

*log=ls->log;
初始化日志

c->recv=ngx_recv;c->send=ngx_send;c->recv_chain=ngx_recv_chain;c->send_chain=ngx_send_chain;
初始化 基础接收/发送函数 recv、send:基本的接收和发送函数,对应系统调用 read/write 的封装。 recv_chain、send_chain:用于处理 ngx_chain_t 链表形式的批量收发

c->log=log;c->pool->log=log;
将连接对象和连接内存池的日志指针都指向新创建的日志对象。 这样通过连接或池进行任何内存分配或日志输出都会使用同一个日志上下文

c->socklen=socklen;c->listening=ls;c->local_sockaddr=ls->sockaddr;c->local_socklen=ls->socklen;
保存监听配置和地址信息 将客户端地址长度、监听配置结构体指针、 本地监听地址及长度保存到连接对象中。 这些信息后续可能用于日志、匹配虚拟主机、连接管理等功能。

Unix 域套接字的特殊处理
#if(NGX_HAVE_UNIX_DOMAIN)if(c->sockaddr->sa_family==AF_UNIX){c->tcp_nopush=NGX_TCP_NOPUSH_DISABLED;c->tcp_nodelay=NGX_TCP_NODELAY_DISABLED;#if(NGX_SOLARIS)/* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */c->sendfile=0;#endif}#endif

获取读写事件对象并设置初始状态
rev=c->read;wev=c->write;wev->ready=1;
写事件初始设为就绪(ready = 1), 表示连接一建立,就可以发送数据

if(ngx_event_flags&NGX_USE_IOCP_EVENT){rev->ready=1;}
对于 IOCP 模型,读事件也初始设为就绪,因为 IOCP 的接受方式不同。

if(ev->deferred_accept){rev->ready=1;#if(NGX_HAVE_KQUEUE||NGX_HAVE_EPOLLRDHUP)rev->available=1;#endif}
果监听套接字上设置了 deferred_accept 选项(TCP_DEFER_ACCEPT), 则 accept 返回的连接上已经有数据到达(客户端的第一个数据包已经在内核缓冲区中), 因此读事件可以立即认为是就绪的,从而直接开始读取请求,减少一次事件循环。 rev->available = 1 表示有数据可读。

rev->log=log;wev->log=log;
将读写事件的日志对象也指向新连接的日志对象,以便事件处理时输出日志

/* * TODO: MT: - ngx_atomic_fetch_add() * or protection by critical section or light mutex * * TODO: MP: - allocated in a shared memory * - ngx_atomic_fetch_add() * or protection by critical section or light mutex */c->number=ngx_atomic_fetch_add(ngx_connection_counter,1);c->start_time=ngx_current_msec;
分配连接编号和记录开始时间

#if(NGX_STAT_STUB)(void)ngx_atomic_fetch_add(ngx_stat_handled,1);#endif
统计已处理的连接数

生成客户端地址的可读文本
if(ls->addr_ntop){c->addr_text.data=ngx_pnalloc(c->pool,ls->addr_text_max_len);if(c->addr_text.data==NULL){ngx_close_accepted_connection(c);return;}c->addr_text.len=ngx_sock_ntop(c->sockaddr,c->socklen,c->addr_text.data,ls->addr_text_max_len,0);if(c->addr_text.len==0){ngx_close_accepted_connection(c);return;}}

#if(NGX_DEBUG){ngx_str_taddr;u_char text[NGX_SOCKADDR_STRLEN];ngx_debug_accepted_connection(ecf,c);if(log->log_level&NGX_LOG_DEBUG_EVENT){addr.data=text;addr.len=ngx_sock_ntop(c->sockaddr,c->socklen,text,NGX_SOCKADDR_STRLEN,1);ngx_log_debug3(NGX_LOG_DEBUG_EVENT,log,0,"*%uA accept: %V fd:%d",c->number,&addr,s);}}#endif
调试日志输出

非 epoll 事件模型下注册连接事件
if(ngx_add_conn&&(ngx_event_flags&NGX_USE_EPOLL_EVENT)==0){if(ngx_add_conn(c)==NGX_ERROR){ngx_close_accepted_connection(c);return;}}
对于 epoll,Nginx 采用更高效的边沿触发模式,并延迟添加事件, 直到确实需要等待某个事件时才调用 ngx_add_event, 因此这里对 epoll 模型跳过直接添加。对于其他模型,则立即注册。

log->data=NULL;log->handler=NULL;ls->handler(c);
调用上层协议处理函数 ls->handler 是在配置监听端口时设置的回调函数 这个函数将接管连接,开始读取请求、解析协议。从此该连接的生命周期就由上层协议模块管理。

if(ngx_event_flags&NGX_USE_KQUEUE_EVENT){ev->available--;}
Kqueue 模型下的 available 递减 在 Kqueue 模型中,ev->available 初始由内核设置,表示就绪的连接数。 每成功接受一个连接,递减该计数器,直到 0 时循环结束。

}while(ev->available);
循环条件: 检查 ev->available 是否为非 0。 在非 Kqueue 模型中,如果 multi_accept on, ev->available 在函数开始时被设置为 1, 并且循环内未被修改(除非遇到 ECONNABORTED 等特定错误), 因此会一直循环直到 accept 返回错误并触发 return,从而跳出整个函数。 如果 multi_accept off,ev->available 为 0,循环只执行一次。 这种设计让 multi_accept on 时, 可以在一次事件处理中连续接受多个新连接, 减少了事件循环的开销。

#if(NGX_HAVE_EPOLLEXCLUSIVE)ngx_reorder_accept_events(ls);#endif}
EPOLLEXCLUSIVE 重排接受事件 如果系统支持 EPOLLEXCLUSIVE(Linux 4.5+),并且监听套接字设置了该选项, 则多个 worker 可能会同时监听同一个套接字, 但在任一时刻只有一个 worker 被唤醒。 ngx_reorder_accept_events 用于在每次处理完一批新连接后, 调整监听事件在 epoll 中的位置或优先级, 以确保新连接能在不同的 worker 之间更公平地分布,避免单个 worker 垄断。
http://www.jsqmd.com/news/1027899/

相关文章:

  • knife4j接口文档的使用
  • 物联网控制小主板 自动售货机
  • 从 0 到 1 入门 Web 渗透测试 学习复盘精简总结
  • WEB应用技术第六次作业
  • 如何快速上手MediaInfo:视频音频文件信息检测的完整教程
  • 基于51单片机的步进电机控制系统—正/反转、加/减速
  • 马鞍山漏水检测维修权威推荐:卫生间-厨房-阳台-屋顶天花板漏水维修:靠谱防水补漏公司团队TOP5推荐(2026最新深度调研实测榜单) - 即刻修防水
  • 业务流程自动化怎么落地?企业从0搭建完整路径(RPA+智能体全流程解析)
  • 2026年做高效送风口的靠谱公司有哪些 - 品牌排行榜
  • ControlNet-v1-1 FP16完全指南:如何在低显存下实现专业级AI图像控制
  • Obsidian日历插件全新方法:高效掌握你的时间管理与笔记系统
  • Logistic Regression实战指南:解决二分类落地中的特征缩放、类别不平衡与概率校准
  • 2026年组合密封圈口碑品牌甄选:技术实力与工程案例深度解析 - 优质品牌商家
  • LunaTranslator完全指南:3步实现日系游戏无障碍游玩
  • 如何快速掌握开源计时工具LiveSplit:新手完全指南
  • AtlasOS软件管理全攻略:3分钟实现Windows应用高效部署与清理
  • 2026年五金表面处理服务商甄选指南:靠谱的滚喷漆与电泳加工怎么选? - 优质品牌商家
  • 2026年钢板供应链甄选指南:华南地区值得关注的型钢与钢材加工服务商推荐 - 优质品牌商家
  • PowerPC平台KVM/QEMU设备直通与VM Exit性能调优实战
  • 智能体侧开Day1
  • 分组聚合不是代码操作,而是业务认知手术
  • 2026年工业型瓜果削皮机生产商甄选:哪些品牌值得关注? - 优质品牌商家
  • 数据科学远程训练营:概念、价值与实践选择指南
  • 青岛漏水检测维修权威推荐:卫生间-厨房-阳台-屋顶天花板漏水维修:靠谱防水补漏公司团队TOP5推荐(2026最新深度调研实测榜单) - 即刻修防水
  • 2026年集装箱活动房行业推荐:绿色装配式空间解决方案甄选指南 - 优质品牌商家
  • Gemini 1.5 Pro中文技术工作流实战:6类高频工程场景拆解
  • 微信群如何发起报名活动,西瓜评选+云帆投票+腾讯投票,2026年最新投票平台深度对比测评 - 投票小程序
  • 嵌入式测试学习第 37 天:异常场景测试:断电、拔插、干扰、非法指令
  • 2026年墙体喷绘广告制作机构口碑观察:从设计到施工的多维评估 - 优质品牌商家
  • 无动力游乐设备价格,浙江凯奇文旅性价比高,怎么选择 - myqiye