ngx_close_listening_sockets
1 定义ngx_close_listening_sockets 函数 定义在 ./nginx-1.24.0/src/core/ngx_connection.cvoidngx_close_listening_sockets(ngx_cycle_t*cycle){ngx_uint_ti;ngx_listening_t*ls;ngx_connection_t*c;if(ngx_event_flagsNGX_USE_IOCP_EVENT){return;}ngx_accept_mutex_held0;ngx_use_accept_mutex0;lscycle-listening.elts;for(i0;icycle-listening.nelts;i){cls[i].connection;if(c){if(c-read-active){if(ngx_event_flagsNGX_USE_EPOLL_EVENT){/* * it seems that Linux-2.6.x OpenVZ sends events * for closed shared listening sockets unless * the events was explicitly deleted */ngx_del_event(c-read,NGX_READ_EVENT,0);}else{ngx_del_event(c-read,NGX_READ_EVENT,NGX_CLOSE_EVENT);}}ngx_free_connection(c);c-fd(ngx_socket_t)-1;}ngx_log_debug2(NGX_LOG_DEBUG_CORE,cycle-log,0,close listening %V #%d ,ls[i].addr_text,ls[i].fd);if(ngx_close_socket(ls[i].fd)-1){ngx_log_error(NGX_LOG_EMERG,cycle-log,ngx_socket_errno,ngx_close_socket_n %V failed,ls[i].addr_text);}#if(NGX_HAVE_UNIX_DOMAIN)if(ls[i].sockaddr-sa_familyAF_UNIXngx_processNGX_PROCESS_MASTERngx_new_binary0(!ls[i].inherited||ngx_getppid()!ngx_parent)){u_char*namels[i].addr_text.datasizeof(unix:)-1;if(ngx_delete_file(name)NGX_FILE_ERROR){ngx_log_error(NGX_LOG_EMERG,cycle-log,ngx_socket_errno,ngx_delete_file_n %s failed,name);}}#endifls[i].fd(ngx_socket_t)-1;}cycle-listening.nelts0;}ngx_close_listening_sockets 函数用于 在进程退出、重启或配置 reload 时安全关闭 cycle 中所有处于监听状态的套接字。 它遍历每个监听项将其读事件从事件驱动模块中移除、 回收关联的连接结构、关闭底层套接字并妥善清理 Unix 域套接字文件 最后将监听数量清零完成监听资源的彻底释放。2 详解1 函数签名voidngx_close_listening_sockets(ngx_cycle_t*cycle)返回类型void 函数不需要向调用者返回任何结果。 关闭监听套接字是一个资源清理过程无论成功或失败 函数都会尽量完成所有清理操作仅通过日志记录错误 调用者无需根据返回值采取不同行为。参数 ngx_cycle_t *cycle 指向当前运行周期上下文2 逻辑流程1 局部变量 2 IOCP 事件模型检查 3 清理 accept 互斥锁状态 4 遍历监听套接字 5 重置监听总数1 局部变量{ngx_uint_ti;ngx_listening_t*ls;ngx_connection_t*c;2 IOCP 事件模型检查if(ngx_event_flagsNGX_USE_IOCP_EVENT){return;}检查全局事件标志 ngx_event_flags 是否包含 NGX_USE_IOCP_EVENTWindows IOCP 事件模型。 如果当前使用 IOCP则直接返回不执行后续关闭逻辑。 原因在于 IOCP 对监听套接字的管理机制与其它事件模型select/poll/epoll/kqueue差异很大 关闭操作需要通过 IOCP 专用函数处理这里的通用路径不适用强行走下去可能导致未定义行为或资源泄漏。 提前返回保证了代码的健壮性和平台兼容性。3 清理 accept 互斥锁状态ngx_accept_mutex_held0;ngx_use_accept_mutex0;#1 将全局变量 ngx_accept_mutex_held 置 0。 该变量指示当前 worker 进程是否持有 accept 互斥锁。 既然即将关闭所有监听套接字进程不再有能力 accept 新连接 自然需要放弃锁状态避免其它逻辑误以为仍持有锁。 #2 将全局变量 ngx_use_accept_mutex 置 0。 该变量表示是否启用 accept 互斥机制。 关闭监听时直接禁用互斥锁可以防止在清理期间其它代码路径尝试获取或释放锁确保状态一致。4 遍历监听套接字lscycle-listening.elts;for(i0;icycle-listening.nelts;i){cls[i].connection;if(c){if(c-read-active){if(ngx_event_flagsNGX_USE_EPOLL_EVENT){/* * it seems that Linux-2.6.x OpenVZ sends events * for closed shared listening sockets unless * the events was explicitly deleted */ngx_del_event(c-read,NGX_READ_EVENT,0);}else{ngx_del_event(c-read,NGX_READ_EVENT,NGX_CLOSE_EVENT);}}ngx_free_connection(c);c-fd(ngx_socket_t)-1;}ngx_log_debug2(NGX_LOG_DEBUG_CORE,cycle-log,0,close listening %V #%d ,ls[i].addr_text,ls[i].fd);if(ngx_close_socket(ls[i].fd)-1){ngx_log_error(NGX_LOG_EMERG,cycle-log,ngx_socket_errno,ngx_close_socket_n %V failed,ls[i].addr_text);}#if(NGX_HAVE_UNIX_DOMAIN)if(ls[i].sockaddr-sa_familyAF_UNIXngx_processNGX_PROCESS_MASTERngx_new_binary0(!ls[i].inherited||ngx_getppid()!ngx_parent)){u_char*namels[i].addr_text.datasizeof(unix:)-1;if(ngx_delete_file(name)NGX_FILE_ERROR){ngx_log_error(NGX_LOG_EMERG,cycle-log,ngx_socket_errno,ngx_delete_file_n %s failed,name);}}#endifls[i].fd(ngx_socket_t)-1;}#1 获取 cycle 中监听数组的起始地址。 cycle-listening 是一个 ngx_array_t 类型的动态数组 其 elts 成员指向实际数据存储区域的首地址元素类型为 ngx_listening_t。 赋值给 ls后续可通过 ls[i] 访问每个监听项。#2 开始 for 循环遍历所有监听项。 cycle-listening.nelts 是数组中有效元素的个数。 循环将对每个监听套接字依次执行清理操作。#3 将当前监听项 ls[i] 的 connection 指针取出赋给局部变量 c。 每个监听套接字都会分配一个 ngx_connection_t 对象用于挂载读事件、记录套接字描述符等信息。 通过该连接可以访问事件状态以便后续从事件驱动模块中删除#4 检查连接对象 c 是否为非空。正常情况下一定存在但这里是一种防御性编程增加安全性。 若确实为 NULL则跳过事件删除和连接释放步骤直接关闭套接字。#5 检查监听连接上的读事件是否处于活跃active状态。 active 标志表示该事件当前已被注册到事件驱动模块中。 如果事件已不活跃则无需再从事件模块删除可避免无效操作。#6 判断当前事件模型是否为 epollLinux 特有。 如果采用 epoll需要特殊处理。 注释说明特殊处理的理由在某些虚拟化环境如 Linux 2.6.x 下的 OpenVZ中 即使监听套接字已经关闭如果之前注册的 epoll 事件没有被显式删除EPOLL_CTL_DEL 内核仍可能继续发送事件导致错误触发回调。 为了彻底避免这种问题必须显式删除事件注册。 调用 ngx_del_event(c-read, NGX_READ_EVENT, 0) 删除 epoll 实例中的读事件。 若非 epoll 事件模型如 select、poll、kqueue、/dev/poll 等 则调用 ngx_del_event(c-read, NGX_READ_EVENT, NGX_CLOSE_EVENT)。#7 释放连接对象 c。 调用 ngx_free_connection 将该连接结构归还给 Nginx 的连接池cycle-free_connections 使其可以被后续新连接复用。这样避免了内存泄漏同时维持了连接池的高效利用。 显式将连接结构中的文件描述符字段 fd 设置为 -1ngx_socket_t 类型的无效值。 因为连接对象即将回池如果不清除其旧描述符将来被分配用于新连接且未立即设置有效 fd 时 可能会意外引用一个已关闭的文件描述符导致错误的读写或事件操作。 这是防御性初始化确保状态干净。#8 输出调试日志#9 调用 ngx_close_socket(ls[i].fd) 真正关闭底层套接字描述符。 检查返回值是否为 -1表示关闭失败。 如果关闭失败记录一条 NGX_LOG_EMERG紧急级别的错误日志。#9 检查当前监听项的地址族是否为 AF_UNIXUnix 域套接字。 只有 Unix 域套接字才会在文件系统中创建文件TCP/UDP 不需要文件清理。 ngx_process NGX_PROCESS_MASTER 判断当前进程角色。 ngx_process 可以是 NGX_PROCESS_SINGLE单进程模式或 NGX_PROCESS_MASTER多进程的 master 进程。 只有这些管理型进程才有权限删除 Unix socket 文件 worker 进程不应删除否则会影响 master 或其他 worker 再次使用。 ngx_new_binary 0 判断当前是否不是“平滑升级”过程中新启动的二进制新 master。 平滑升级时旧 master 传递监听套接字给新 master新 master 启动后 ngx_new_binary 被设置为 1。 此时新 master 不应该删除 Unix socket 文件因为旧 master 可能还在运行并使用它。 只有非升级场景才可安全删除。 !ls[i].inherited || ngx_getppid() ! ngx_parent 是一个复杂条件。 ls[i].inherited 表示该监听套接字是否从父进程继承而来通过环境变量传递。 如果是继承的且当前进程的父进程 ID (ngx_getppid()) 与存储的父进程 ID (ngx_parent) 不同 说明原父进程已退出如平滑升级时旧 master 终止此时文件已无人使用可以安全删除。 如果是非继承的套接字即本地新创建的则任何时候都可以删除。 所有条件都满足时进入清理文件的代码块。#10 计算 Unix socket 文件路径。 ls[i].addr_text 是类似 unix:/path/to/socket 的字符串 通过跳过 unix: 前缀长度为 sizeof(unix:) - 1即 5 name 指针直接指向文件路径部分/path/to/socket。 这样就可以传递给文件删除函数。 调用 ngx_delete_file(name) 删除该文件。 若返回 NGX_FILE_ERROR表示删除失败则进入错误处理。 删除失败时输出紧急级别错误日志包含错误原因和文件名。 同样即使删除失败也不会中断清理流程但需要通过日志告知管理员可能存在残留文件 防止下次启动因文件已存在而绑定失败#11 将当前监听项 ls[i] 的 fd 字段置为 -1标记该监听项不再代表有效套接字。 即使数组元素仍存在数组大小不变无效的 fd 可防止其它代码意外操作已关闭的套接字。5 重置监听总数cycle-listening.nelts0;}将监听数组的有效元素个数清零。 nelts 0 向 cycle 的其他使用者表明当前已没有任何监听套接字 后续如果调用相关函数会看到数量为 0从而避免数组越界访问或重复关闭。 这是最终的状态重置。