Ubuntu 下 nginx-1.24.0 源码分析 - ngx_set_inherited_sockets
ngx_set_inherited_sockets
声明在 src/core/ngx_connection.h
ngx_int_t ngx_set_inherited_sockets(ngx_cycle_t *cycle);
实现在 src/core/ngx_connection.c
ngx_int_t ngx_set_inherited_sockets(ngx_cycle_t *cycle) { size_t len; ngx_uint_t i; ngx_listening_t *ls; socklen_t olen; #if (NGX_HAVE_DEFERRED_ACCEPT || NGX_HAVE_TCP_FASTOPEN) ngx_err_t err; #endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) struct accept_filter_arg af; #endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) int timeout; #endif #if (NGX_HAVE_REUSEPORT) int reuseport; #endif ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { ls[i].sockaddr = ngx_palloc(cycle->pool, sizeof(ngx_sockaddr_t)); if (ls[i].sockaddr == NULL) { return NGX_ERROR; } ls[i].socklen = sizeof(ngx_sockaddr_t); if (getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen) == -1) { ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, "getsockname() of the inherited " "socket #%d failed", ls[i].fd); ls[i].ignore = 1; continue; } if (ls[i].socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { ls[i].socklen = sizeof(ngx_sockaddr_t); } switch (ls[i].sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: ls[i].addr_text_max_len = NGX_INET6_ADDRSTRLEN; len = NGX_INET6_ADDRSTRLEN + sizeof("[]:65535") - 1; break; #endif #if (NGX_HAVE_UNIX_DOMAIN) case AF_UNIX: ls[i].addr_text_max_len = NGX_UNIX_ADDRSTRLEN; len = NGX_UNIX_ADDRSTRLEN; break; #endif case AF_INET: ls[i].addr_text_max_len = NGX_INET_ADDRSTRLEN; len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1; break; default: ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, "the inherited socket #%d has " "an unsupported protocol family", ls[i].fd); ls[i].ignore = 1; continue; } ls[i].addr_text.data = ngx_pnalloc(cycle->pool, len); if (ls[i].addr_text.data == NULL) { return NGX_ERROR; } len = ngx_sock_ntop(ls[i].sockaddr, ls[i].socklen, ls[i].addr_text.data, len, 1); if (len == 0) { return NGX_ERROR; } ls[i].addr_text.len = len; ls[i].backlog = NGX_LISTEN_BACKLOG; olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_TYPE, (void *) &ls[i].type, &olen) == -1) { ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, "getsockopt(SO_TYPE) %V failed", &ls[i].addr_text); ls[i].ignore = 1; continue; } olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF, (void *) &ls[i].rcvbuf, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_RCVBUF) %V failed, ignored", &ls[i].addr_text); ls[i].rcvbuf = -1; } olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF, (void *) &ls[i].sndbuf, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_SNDBUF) %V failed, ignored", &ls[i].addr_text); ls[i].sndbuf = -1; } #if 0 /* SO_SETFIB is currently a set only option */ #if (NGX_HAVE_SETFIB) olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB, (void *) &ls[i].setfib, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_SETFIB) %V failed, ignored", &ls[i].addr_text); ls[i].setfib = -1; } #endif #endif #if (NGX_HAVE_REUSEPORT) reuseport = 0; olen = sizeof(int); #ifdef SO_REUSEPORT_LB if (getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT_LB, (void *) &reuseport, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_REUSEPORT_LB) %V failed, ignored", &ls[i].addr_text); } else { ls[i].reuseport = reuseport ? 1 : 0; } #else if (getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT, (void *) &reuseport, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_REUSEPORT) %V failed, ignored", &ls[i].addr_text); } else { ls[i].reuseport = reuseport ? 1 : 0; } #endif #endif if (ls[i].type != SOCK_STREAM) { continue; } #if (NGX_HAVE_TCP_FASTOPEN) olen = sizeof(int); if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN, (void *) &ls[i].fastopen, &olen) == -1) { err = ngx_socket_errno; if (err != NGX_EOPNOTSUPP && err != NGX_ENOPROTOOPT && err != NGX_EINVAL) { ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, "getsockopt(TCP_FASTOPEN) %V failed, ignored", &ls[i].addr_text); } ls[i].fastopen = -1; } #endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) ngx_memzero(&af, sizeof(struct accept_filter_arg)); olen = sizeof(struct accept_filter_arg); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, &olen) == -1) { err = ngx_socket_errno; if (err == NGX_EINVAL) { continue; } ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, "getsockopt(SO_ACCEPTFILTER) for %V failed, ignored", &ls[i].addr_text); continue; } if (olen < sizeof(struct accept_filter_arg) || af.af_name[0] == '\0') { continue; } ls[i].accept_filter = ngx_palloc(cycle->pool, 16); if (ls[i].accept_filter == NULL) { return NGX_ERROR; } (void) ngx_cpystrn((u_char *) ls[i].accept_filter, (u_char *) af.af_name, 16); #endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) timeout = 0; olen = sizeof(int); if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, &olen) == -1) { err = ngx_socket_errno; if (err == NGX_EOPNOTSUPP) { continue; } ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, "getsockopt(TCP_DEFER_ACCEPT) for %V failed, ignored", &ls[i].addr_text); continue; } if (olen < sizeof(int) || timeout == 0) { continue; } ls[i].deferred_accept = 1; #endif } return NGX_OK; }
函数签名
ngx_int_t ngx_set_inherited_sockets(ngx_cycle_t *cycle)
返回值类型为
ngx_int_t
,表示函数执行结果(成功或失败)参数
cycle
是 Nginx 的核心数据结构,包含运行时的全局配置和状态信息
变量声明
size_t len;
len 是一个无符号整数类型(size_t),用于存储地址字符串的长度。
在后续代码中,len 会被用来计算可读地址文本(如 127.0.0.1:80)所需的缓冲区大小。
避免硬编码缓冲区大小,动态计算以提高灵活性和安全性。
ngx_uint_t i;
i 是一个无符号整数类型(ngx_uint_t),用作循环计数器。
后续代码会遍历 cycle->listening.elts 数组中的每个监听套接字,i 就是用来索引这些套接字的变量。
ngx_listening_t *ls;
ls 是一个指向 ngx_listening_t 类型的指针,表示监听套接字数组。
cycle->listening.elts 是一个数组,存储了所有监听套接字的信息。通过将 ls 指向这个数组,可以在循环中方便地访问每个监听套接字。
socklen_t olen;
olen 是一个 socklen_t 类型变量,用于存储套接字选项的长度。
在调用 getsockopt 函数时,需要传递一个变量来接收选项值的长度。olen 就是这个变量。
确保在获取套接字选项时,能够正确处理选项值的长度,避免越界或错误。
#if (NGX_HAVE_DEFERRED_ACCEPT || NGX_HAVE_TCP_FASTOPEN) ngx_err_t err; #endif
如果系统支持延迟接受(deferred accept)或 TCP 快速打开(TCP Fast Open),则定义一个 ngx_err_t 类型的变量 err。
err 用于存储系统调用(如 getsockopt)返回的错误码。如果发生错误,可以通过 err 判断具体原因。
提高代码的可移植性,仅在支持相关功能的平台上定义变量,避免浪费资源。
NGX_HAVE_DEFERRED_ACCEPT
宏表示当前操作系统是否支持 延迟接受(Deferred Accept) 功能。延迟接受是一种优化技术,允许内核在接收到客户端的第一个数据包之前不通知应用程序有新的连接到达。
这种机制可以减少不必要的系统调用,提升服务器性能。
在传统的 TCP 连接处理中,当客户端发起连接时,内核会立即通知应用程序(通过 accept() 系统调用),即使客户端尚未发送任何数据。
这种行为可能导致大量的空闲连接被创建,增加了服务器的负担。延迟接受允许内核在客户端发送第一个数据包后再通知应用程序。这样可以避免为无用的连接分配资源,特别适用于高并发场景。
如果
NGX_HAVE_DEFERRED_ACCEPT
被定义,Nginx 会在初始化监听套接字时检查并启用延迟接受功能
NGX_HAVE_TCP_FASTOPEN
宏表示当前操作系统是否支持 TCP 快速打开(TCP Fast Open, TFO) 功能。TCP 快速打开是一种优化技术,允许客户端在建立 TCP 连接的同时发送数据,从而减少往返时间(RTT)
传统 TCP 握手过程 :
在传统的 TCP 连接中,客户端需要完成三次握手后才能发送数据。这会导致额外的延迟(至少一个 RTT)
TCP 快速打开的作用 :
TCP 快速打开允许客户端在 SYN 包中携带数据,服务器可以在握手完成之前就开始处理数据。
这种机制特别适用于短连接场景(如 HTTP 请求),可以显著减少延迟。
如果 NGX_HAVE_TCP_FASTOPEN 被定义,Nginx 会在初始化监听套接字时检查并启用 TCP 快速打开功能。
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) struct accept_filter_arg af; #endif
如果系统支持延迟接受,并且定义了 SO_ACCEPTFILTER 宏,则定义一个 struct accept_filter_arg 类型的变量 af
SO_ACCEPTFILTER 是 FreeBSD 和某些类 Unix 系统特有的套接字选项,用于实现延迟接受。af 用于存储该选项的具体参数。
为特定平台提供优化功能,同时保持代码的跨平台兼容性。
SO_ACCEPTFILTER
是 FreeBSD 和某些类 Unix 系统特有的套接字选项,用于实现延迟接受功能它允许应用程序为监听套接字附加一个过滤器(Accept Filter),以控制何时通知应用程序有新的连接到达
工作原理
当启用 SO_ACCEPTFILTER 后,内核会根据指定的过滤器规则决定是否将连接传递给应用程序。
常见的过滤器包括:
httpready:等待 HTTP 请求的第一个字节后再通知应用程序。
dataready:等待任意数据到达后再通知应用程序。
defined SO_ACCEPTFILTER 表示编译器需要检查系统头文件中是否存在 SO_ACCEPTFILTER 宏。
如果存在,则说明当前操作系统支持该功能。
struct accept_filter_arg
是一个结构体,用于存储SO_ACCEPTFILTER
的配置参数
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) int timeout; #endif
如果系统支持延迟接受,并且定义了 TCP_DEFER_ACCEPT 宏,则定义一个 int 类型的变量 timeout。
TCP_DEFER_ACCEPT 是 Linux 特有的套接字选项,用于延迟接受连接直到有数据到达。timeout 用于存储超时时间。
为 Linux 平台提供延迟接受功能,同时不影响其他平台的代码。
TCP_DEFER_ACCEPT
TCP_DEFER_ACCEPT 是 Linux 特有的套接字选项,用于实现延迟接受功能。
当启用 TCP_DEFER_ACCEPT 后,内核会等待客户端发送第一个数据包后再通知应用程序有新的连接到达。如果客户端在指定的时间内没有发送数据,则连接会被丢弃。
TCP_DEFER_ACCEPT 的超时时间以秒为单位,表示内核等待客户端发送数据的最大时间。
如果客户端在超时时间内发送了数据,内核会立即通知应用程序。
如果超时时间内没有收到数据,内核会丢弃连接,避免浪费资源。
defined TCP_DEFER_ACCEPT 表示编译器需要检查系统头文件中是否存在 TCP_DEFER_ACCEPT 宏。
如果存在,则说明当前操作系统支持该功能。
int timeout
timeout 是一个整数类型的变量,用于存储 TCP_DEFER_ACCEPT 的超时时间(以秒为单位)。
它的具体值通常通过 getsockopt() 函数从监听套接字中获取。
在 Nginx 中,timeout 用于检查监听套接字是否启用了 TCP_DEFER_ACCEPT,并获取其超时时间。
#if (NGX_HAVE_REUSEPORT) int reuseport; #endif
如果系统支持端口复用(SO_REUSEPORT 或 SO_REUSEPORT_LB),则定义一个 int 类型的变量 reuseport。
SO_REUSEPORT 允许多个进程绑定到同一个端口,从而提高性能和扩展性。reuseport 用于存储该选项的状态(启用或禁用)。
支持现代操作系统提供的高级功能,提升 Nginx 的并发处理能力。
NGX_HAVE_REUSEPORT
表示当前操作系统是否支持 端口复用(SO_REUSEPORT) 功能。
端口复用是一种优化技术,允许多个进程或线程绑定到同一个端口,从而提高服务器的并发处理能力。
传统行为 :
在传统的网络编程中,多个进程或线程无法同时绑定到同一个端口。如果尝试这样做,会触发 "Address already in use" 错误。
端口复用的作用 :
端口复用允许内核在多个进程或线程之间分发连接请求,从而实现负载均衡。
这种机制特别适用于多线程或多进程架构的服务器(如 Nginx 的多 worker 模型),可以显著提升性能和扩展性。
实现方式
Linux :
从 Linux 3.9 开始引入了 SO_REUSEPORT 套接字选项。
内核会为每个绑定到同一端口的套接字维护一个独立的连接队列,并通过负载均衡算法将连接请求分发给不同的进程或线程。
reuseport
reuseport 是一个整数类型的变量,用于存储 SO_REUSEPORT 套接字选项的状态。
它的具体值通常通过 getsockopt() 函数从监听套接字中获取。
reuseport 的值通常是布尔值:
0:表示未启用端口复用。
非零值(通常为 1):表示启用了端口复用。
ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) {
遍历监听套接字数组
cycle->listening.elts 是一个数组,存储了所有监听套接字的描述符和相关信息。
cycle->listening.nelts 表示数组中元素的数量。
循环遍历每个监听套接字,逐一初始化其属性。
ls[i].sockaddr = ngx_palloc(cycle->pool, sizeof(ngx_sockaddr_t)); if (ls[i].sockaddr == NULL) { return NGX_ERROR; } ls[i].socklen = sizeof(ngx_sockaddr_t); if (getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen) == -1) { ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, "getsockname() of the inherited " "socket #%d failed", ls[i].fd); ls[i].ignore = 1; continue; }
分配内存并获取套接字地址
分配内存 :
使用 ngx_palloc 为 ls[i].sockaddr 分配内存,存储套接字地址。
如果分配失败,直接返回错误。
获取套接字地址 :
调用 getsockname 获取套接字的本地地址,并存储到 ls[i].sockaddr 中。
如果调用失败,记录日志并将该套接字标记为忽略(ls[i].ignore = 1)。
确保每个监听套接字都有有效的地址信息,以便后续使用。
ls[i].sockaddr
是一个指针,指向存储套接字地址信息的缓冲区
ls[i].socklen
用于存储套接字地址的实际长度在调用
getsockname()
之前,需要初始化它为最大可能的地址长度(即sizeof(ngx_sockaddr_t)
)确保
getsockname()
调用时能够正确处理地址长度,避免越界或错误
getsockname()
是一个标准的 POSIX 函数,用于获取套接字的本地地址ls[i].fd:监听套接字的文件描述符。
ls[i].sockaddr:存储地址信息的缓冲区。
&ls[i].socklen:输入输出参数,表示缓冲区的大小。调用完成后,ls[i].socklen 会被更新为实际地址的长度ls[i].ignore 是一个布尔标志位,表示该套接字是否应该被忽略。
如果 getsockname() 调用失败,则将 ls[i].ignore 设置为 1,表示该套接字不可用
if (ls[i].socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { ls[i].socklen = sizeof(ngx_sockaddr_t); }
getsockname() 返回的实际地址长度可能会超出我们分配给 ls[i].sockaddr 的缓冲区大小(即 sizeof(ngx_sockaddr_t))。
如果发生这种情况,可能会导致后续操作出现问题,比如缓冲区溢出或数据截断。
因此,这段代码的作用是检查并限制 ls[i].socklen 的值,确保它不会超过缓冲区的大小
ls[i].socklen 表示实际可用的地址长度。如果它超出了缓冲区大小,则意味着部分地址信息无法存储。
通过限制 ls[i].socklen,可以确保后续操作只访问缓冲区内的有效数据不同操作系统和协议可能返回不同大小的地址信息(如 IPv4 地址较小,IPv6 地址较大)
switch (ls[i].sockaddr->sa_family) {
这行代码使用 switch 语句根据套接字地址族(sa_family)来决定如何处理监听套接字的地址信息。
ls[i].sockaddr->sa_family 是一个字段,表示套接字地址的协议族(如 IPv4、IPv6 或 Unix 域套接字)。sa_family 是 struct sockaddr 的第一个字段,定义了地址的类型。
常见的值包括:
AF_INET:表示 IPv4 地址。
AF_INET6:表示 IPv6 地址。
AF_UNIX:表示 Unix 域套接字。
根据不同的地址族,设置相应的最大地址文本长度和缓冲区大小。意图
提供一种通用的方式处理不同协议族的地址信息。
确保后续生成的地址文本(如 127.0.0.1:80 或 [::1]:80)能够正确表示所有支持的协议。
#if (NGX_HAVE_INET6) case AF_INET6: ls[i].addr_text_max_len = NGX_INET6_ADDRSTRLEN; len = NGX_INET6_ADDRSTRLEN + sizeof("[]:65535") - 1; break; #endif
#if (NGX_HAVE_INET6)
这是一个条件编译指令,用于检查当前系统是否支持 IPv6。
如果定义了 NGX_HAVE_INET6 宏,则说明系统支持 IPv6,因此需要处理 AF_INET6 地址族。
case AF_INET6:
case AF_INET6:
这是 switch 语句的一个分支,用于处理 IPv6 地址族(AF_INET6)。
当 ls[i].sockaddr->sa_family 的值为 AF_INET6 时,执行该分支的代码。
在这个分支中,会为 IPv6 地址设置相关的参数。意图
针对 IPv6 地址的特点,设置合适的缓冲区大小和地址文本长度。
ls[i].addr_text_max_len = NGX_INET6_ADDRSTRLEN;
设置 ls[i].addr_text_max_len 的值为 NGX_INET6_ADDRSTRLEN。
addr_text_max_len 表示可读地址文本的最大长度。
NGX_INET6_ADDRSTRLEN 是一个常量,表示 IPv6 地址的最大字符串长度。
意图
确保后续生成的地址文本不会超出最大长度限制。
为 IPv6 地址分配足够的空间,避免截断或溢出。
NGX_INET6_ADDRSTRLEN
定义在 src/core/ngx_inet.h
#define NGX_INET6_ADDRSTRLEN \ (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1)
IPv6 地址是一个 128 位的地址,通常以 16 进制形式表示,并用冒号(:)分隔为 8 组,每组 16 位。
例如:
2001:0db8:85a3:0000:0000:8a2e:0370:7334
此外,IPv6 地址还有以下特性:
简写规则:连续的零可以用双冒号(::)代替,但只能出现一次。
IPv4 映射地址:IPv6 地址可以嵌入 IPv4 地址,格式为 ::ffff:x.x.x.x,其中 x.x.x.x 是 IPv4 地址。例如:
嵌套 IPv4 的 IPv6 地址:::ffff:192.168.1.1
最长的 IPv6 地址:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffffIPv6 地址的最大字符串长度
为了计算 IPv6 地址的最大字符串长度,我们需要考虑最复杂的情况,即:
所有 8 组都填满最大值 ffff。
嵌套 IPv4 地址时,IPv4 部分使用点分十进制表示法。(1) 纯 IPv6 地址
纯 IPv6 地址的最大长度是:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
每组 ffff 占 4 个字符。
冒号分隔符占 7 个字符。
总长度 = 4 * 8 + 7 = 39。(2) 嵌套 IPv4 的 IPv6 地址
嵌套 IPv4 的 IPv6 地址格式为:ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255
前 6 组 ffff 占 4 * 6 = 24 个字符。
冒号分隔符占 6 个字符。
IPv4 部分 255.255.255.255 占 15 个字符。
总长度 = 24 + 6 + 15 = 45。因此,嵌套 IPv4 的 IPv6 地址比纯 IPv6 地址更长,最大长度为 45。
sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") 的值是 46,因为它包含了字符串末尾的 \0。
由于 sizeof 包括了末尾的空字符(\0),而我们只需要计算实际的字符串长度(不包括 \0),所以需要减去 1。
最终结果为:
sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1 = 45
len = NGX_INET6_ADDRSTRLEN + sizeof("[]:65535") - 1;
计算并设置 len 的值,表示完整的地址文本(包括端口号)所需的缓冲区大小。
NGX_INET6_ADDRSTRLEN 表示 IPv6 地址的最大字符串长度。
sizeof("[]:65535") 表示 IPv6 地址的附加部分(方括号和端口号)的长度。
"[]" 用于包裹 IPv6 地址
":65535" 表示端口号的最大长度(包括冒号)。
-1 是因为 sizeof 包括了字符串末尾的空字符 \0,而实际计算时不计入该字符。意图
确保缓冲区足够大,以存储完整的地址文本(如 [::1]:80)。
#if (NGX_HAVE_UNIX_DOMAIN) case AF_UNIX: ls[i].addr_text_max_len = NGX_UNIX_ADDRSTRLEN; len = NGX_UNIX_ADDRSTRLEN; break; #endif
#if (NGX_HAVE_UNIX_DOMAIN)
这是一个条件编译指令,用于检查当前系统是否支持 Unix 域套接字(Unix Domain Sockets)。
如果定义了 NGX_HAVE_UNIX_DOMAIN 宏,则说明系统支持 Unix 域套接字,因此需要处理 AF_UNIX 地址族。意图
提高代码的跨平台兼容性:仅在支持 Unix 域套接字的平台上编译相关代码,避免在不支持的平台上引发编译错误或浪费资源。
case AF_UNIX:
这是 switch 语句的一个分支,用于处理 Unix 域套接字地址族(AF_UNIX)。
当 ls[i].sockaddr->sa_family 的值为 AF_UNIX 时,执行该分支的代码。
在这个分支中,会为 Unix 域套接字地址设置相关的参数。
ls[i].addr_text_max_len = NGX_UNIX_ADDRSTRLEN;
设置 ls[i].addr_text_max_len 的值为 NGX_UNIX_ADDRSTRLEN。
addr_text_max_len 表示可读地址文本的最大长度。
NGX_UNIX_ADDRSTRLEN 是一个常量,表示 Unix 域套接字路径的最大字符串长度。
对于 Unix 域套接字,其地址通常是一个文件路径(如 /tmp/nginx.sock),路径的最大长度由系统限制决定。
例如,在 Linux 系统中,路径的最大长度通常为 108 字节(包括终止符 \0)。意图
确保后续生成的地址文本不会超出最大长度限制。
为 Unix 域套接字路径分配足够的空间,避免截断或溢出。
len = NGX_UNIX_ADDRSTRLEN;
设置 len 的值为 NGX_UNIX_ADDRSTRLEN。
len 表示完整的地址文本所需的缓冲区大小。
对于 Unix 域套接字,地址文本就是文件路径本身,因此缓冲区大小与 addr_text_max_len 相同。
不需要额外的空间来存储端口号或其他附加信息。意图
- 确保缓冲区足够大,以存储完整的 Unix 域套接字路径。
1. #if (NGX_HAVE_UNIX_DOMAIN):
- 条件编译确保仅在支持 Unix 域套接字的平台上编译相关代码。2. case AF_UNIX::
- 处理 Unix 域套接字地址族的具体逻辑。3. ls[i].addr_text_max_len = NGX_UNIX_ADDRSTRLEN;:
- 设置 Unix 域套接字路径的最大长度。4. len = NGX_UNIX_ADDRSTRLEN;:
- 计算完整的地址文本(即文件路径)所需的缓冲区大小。5. break;:
- 确保每个分支的代码独立执行。
case AF_INET: ls[i].addr_text_max_len = NGX_INET_ADDRSTRLEN; len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1; break;
1. case AF_INET:
这是 switch 语句的一个分支,用于处理 IPv4 地址族(AF_INET)。
当 ls[i].sockaddr->sa_family 的值为 AF_INET 时,执行该分支的代码。
在这个分支中,会为 IPv4 地址设置相关的参数。
2. ls[i].addr_text_max_len = NGX_INET_ADDRSTRLEN;
设置 ls[i].addr_text_max_len 的值为 NGX_INET_ADDRSTRLEN。
addr_text_max_len 表示可读地址文本的最大长度。
NGX_INET_ADDRSTRLEN 是一个常量,表示 IPv4 地址的最大字符串长度。
确保后续生成的地址文本不会超出最大长度限制。
为 IPv4 地址分配足够的空间,避免截断或溢出。
3. len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1;
计算并设置 len 的值,表示完整的地址文本(包括端口号)所需的缓冲区大小。
NGX_INET_ADDRSTRLEN 表示 IPv4 地址的最大字符串长度。
sizeof(":65535") 表示 IPv4 地址的附加部分(冒号和端口号)的长度。
":65535" 表示端口号的最大长度(包括冒号)。
端口号的最大值为 65535,因此需要 6 个字符(包括冒号)。
- 1 是因为 sizeof 包括了字符串末尾的空字符 \0,而实际计算时不计入该字符。
确保缓冲区足够大,以存储完整的地址文本(如 192.168.1.1:80)。
1. case AF_INET::
- 处理 IPv4 地址族的具体逻辑。2. ls[i].addr_text_max_len = NGX_INET_ADDRSTRLEN;:
- 设置 IPv4 地址文本的最大长度。3. len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1;:
- 计算完整的地址文本(包括端口号)所需的缓冲区大小。4. break;:
- 确保每个分支的代码独立执行。
这段代码的核心目的是为 IPv4 地址设置合适的参数,以便后续生成可读的地址文本(如 192.168.1.1:80)
NGX_INET_ADDRSTRLEN
定义在 src/core/ngx_inet.h
#define NGX_INET_ADDRSTRLEN (sizeof("255.255.255.255") - 1)
default: ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, "the inherited socket #%d has " "an unsupported protocol family", ls[i].fd); ls[i].ignore = 1; continue; }
1. default:
这是 switch 语句的一个分支,用于处理所有未匹配的地址族(即不属于 AF_INET、AF_INET6 或 AF_UNIX 的协议族)。
当 ls[i].sockaddr->sa_family 的值不是已知的协议族时,执行该分支的代码。
这种情况通常表示监听套接字使用了 Nginx 不支持的协议族。
2. ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, ...)
记录一条日志,说明当前监听套接字的协议族不受支持。
日志级别为 NGX_LOG_CRIT,表示这是一个严重的错误。提供详细的错误信息,便于调试和排查问题。
帮助开发者快速定位问题的根源(例如,某个套接字使用了不受支持的协议族)。
3. ls[i].ignore = 1;
将当前监听套接字标记为忽略状态。
ls[i].ignore 是一个布尔标志位,表示该套接字是否应该被忽略。
如果协议族不受支持,则将 ls[i].ignore 设置为 1,表示该套接字不可用。避免后续代码对无效套接字进行操作
4. continue;
跳过当前循环的剩余部分,继续处理下一个监听套接字。
如果当前套接字被标记为忽略,则不需要继续对其进行初始化或配置。
直接跳过当前迭代,进入下一次循环。
1. default::
- 处理所有未匹配的地址族(即不受支持的协议族)。2. ngx_log_error(...):
- 记录详细的错误日志,说明当前套接字的协议族不受支持。3. ls[i].ignore = 1;:
- 将当前套接字标记为忽略状态,避免后续操作。4. continue;:
- 跳过当前套接字,继续处理下一个监听套接字。
ls[i].addr_text.data = ngx_pnalloc(cycle->pool, len); if (ls[i].addr_text.data == NULL) { return NGX_ERROR; }
1. ls[i].addr_text.data = ngx_pnalloc(cycle->pool, len);
这行代码为 ls[i].addr_text.data 分配内存。
ls[i].addr_text.data 是一个指针,指向存储可读地址文本的缓冲区。
len 是之前计算出的缓冲区大小,表示完整的地址文本所需的内存空间。
2. if (ls[i].addr_text.data == NULL) { return NGX_ERROR; }
检查 ngx_pnalloc 是否成功分配了内存。
如果分配失败(返回 NULL),直接返回错误码 NGX_ERROR。
内存分配失败可能是因为内存不足或其他系统问题。
在这种情况下,继续执行会导致未定义行为,因此需要立即终止函数。
1. ls[i].addr_text.data = ngx_pnalloc(cycle->pool, len);:
为可读地址文本分配内存。2. if (ls[i].addr_text.data == NULL) { return NGX_ERROR; }:
检查内存分配是否成功。
len = ngx_sock_ntop(ls[i].sockaddr, ls[i].socklen, ls[i].addr_text.data, len, 1); if (len == 0) { return NGX_ERROR; }
1. len = ngx_sock_ntop(ls[i].sockaddr, ls[i].socklen, ls[i].addr_text.data, len, 1);
调用 ngx_sock_ntop 函数将二进制形式的套接字地址(如 IPv4 或 IPv6 地址)转换为可读的文本形式(如 192.168.1.1:80 或 [::1]:80),并存储到 ls[i].addr_text.data 中。
返回值是生成的地址文本的实际长度。
参数说明:
ls[i].sockaddr:指向存储套接字地址信息的缓冲区(如 IPv4 或 IPv6 地址)。
ls[i].socklen:表示套接字地址的实际长度。
ls[i].addr_text.data:指向存储可读地址文本的缓冲区。
len:缓冲区的大小,确保不会发生溢出。
1:一个标志位,通常用于指示是否包含端口号。如果值为 1,则生成的地址文本会包含端口号(如 192.168.1.1:80);如果值为 0,则不包含端口号。意图
将二进制地址转换为人类可读的文本形式,便于日志记录、调试和显示。
确保生成的地址文本不会超出缓冲区大小,避免溢出或截断。
2. if (len == 0) { return NGX_ERROR; }
检查 ngx_sock_ntop 的返回值是否为 0。
如果返回值为 0,说明地址转换失败,直接返回错误码 NGX_ERROR。
ngx_sock_ntop 返回值的意义:
如果返回值大于 0,表示成功生成了地址文本,并返回其实际长度。
如果返回值为 0,表示地址转换失败(例如,输入的地址格式无效或缓冲区不足)
在这种情况下,继续执行会导致未定义行为,因此需要立即终止函数。
1. len = ngx_sock_ntop(...):
将二进制地址转换为可读的文本形式,并存储到缓冲区中。
返回生成的地址文本的实际长度。2. if (len == 0) { return NGX_ERROR; }:
检查地址转换是否成功。
如果失败,立即返回错误码,避免后续操作引发问题。
ls[i].addr_text.len = len; ls[i].backlog = NGX_LISTEN_BACKLOG;
1. ls[i].addr_text.len = len;
将生成的地址文本的实际长度(len)存储到 ls[i].addr_text.len 中。
ls[i].addr_text.len 是一个字段,表示可读地址文本的长度。
在上一步中,ngx_sock_ntop 函数成功将二进制地址转换为可读的文本形式,并返回了实际生成的文本长度(len)。
这里将该长度值存储到 ls[i].addr_text.len 中,以便后续使用。
2. ls[i].backlog = NGX_LISTEN_BACKLOG;
设置监听套接字的积压队列大小(backlog)为 NGX_LISTEN_BACKLOG。
ls[i].backlog 是一个字段,表示监听套接字的积压队列大小。
backlog 参数用于指定内核为监听套接字维护的未完成连接队列的最大长度。
当客户端发起连接请求时,如果服务器尚未调用 accept() 接受连接,则这些连接会暂时存放在积压队列中。
如果积压队列已满,新的连接请求可能会被拒绝或丢弃。
NGX_LISTEN_BACKLOG 是一个常量,通常定义为一个较大的值(如 511),表示默认的积压队列大小。
提供一个合理的默认值,确保监听套接字能够处理一定数量的并发连接请求。
避免因积压队列过小而导致连接被拒绝的情况。
总结
1. ls[i].addr_text.len = len;:
存储生成的地址文本的实际长度,便于后续使用。
2. ls[i].backlog = NGX_LISTEN_BACKLOG;:
设置监听套接字的积压队列大小,默认值为 NGX_LISTEN_BACKLOG。
确保监听套接字能够处理一定数量的并发连接请求。
olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_TYPE, (void *) &ls[i].type, &olen) == -1) { ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, "getsockopt(SO_TYPE) %V failed", &ls[i].addr_text); ls[i].ignore = 1; continue; }
1. olen = sizeof(int);
初始化变量 olen 的值为 sizeof(int)。
olen 是一个 socklen_t 类型的变量,表示 getsockopt() 函数中选项值的长度。
在调用 getsockopt() 之前,需要初始化 olen 为选项值的最大可能长度(即 sizeof(int))。
getsockopt() 是一个标准的 POSIX 函数,用于获取套接字选项的值。olen 是输入输出参数,表示选项值的大小。
确保 getsockopt() 调用时能够正确处理选项值的长度,避免越界或错误。
2. if (getsockopt(ls[i].fd, SOL_SOCKET, SO_TYPE, (void *) &ls[i].type, &olen) == -1)
调用 getsockopt() 获取监听套接字的类型(如 SOCK_STREAM 或 SOCK_DGRAM),并检查调用是否成功。
如果调用失败(返回 -1),记录错误日志并将该套接字标记为忽略状态。
参数说明:
ls[i].fd:监听套接字的文件描述符。
SOL_SOCKET:指定选项的协议级别,这里表示通用套接字选项。
SO_TYPE:指定要获取的选项,表示套接字的类型。
(void *) &ls[i].type:指向存储选项值的缓冲区,ls[i].type 将保存套接字的类型(如 SOCK_STREAM 或 SOCK_DGRAM)。
&olen:输入输出参数,表示选项值的大小。调用完成后,olen 会被更新为实际选项值的长度。意图
获取监听套接字的类型信息,以便后续根据类型进行不同的处理。
如果调用失败,记录详细的错误信息,并将该套接字标记为忽略状态。
3. ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, ...)
记录一条日志,说明 getsockopt(SO_TYPE) 调用失败的原因。
日志级别为 NGX_LOG_CRIT,表示这是一个严重的错误。
4. ls[i].ignore = 1;
将当前监听套接字标记为忽略状态。
ls[i].ignore 是一个布尔标志位,表示该套接字是否应该被忽略。
如果 getsockopt(SO_TYPE) 调用失败,则将 ls[i].ignore 设置为 1,表示该套接字不可用。
避免后续代码对无效套接字进行操作,确保程序的健壮性。
总结
1. olen = sizeof(int);:
初始化选项值的长度,确保 getsockopt() 调用时能够正确处理。2. if (getsockopt(...)):
获取监听套接字的类型信息
如果调用失败,记录错误日志并跳过该套接字。3. ngx_log_error(...):
记录详细的错误日志,说明 getsockopt(SO_TYPE) 调用失败的原因。4. ls[i].ignore = 1;:
将当前套接字标记为忽略状态,避免后续操作。5. continue;:
跳过当前套接字,继续处理下一个监听套接字。
olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF, (void *) &ls[i].rcvbuf, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_RCVBUF) %V failed, ignored", &ls[i].addr_text); ls[i].rcvbuf = -1; }
1. olen = sizeof(int);
初始化变量 olen 的值为 sizeof(int)。
olen 是一个 socklen_t 类型的变量,表示 getsockopt() 函数中选项值的长度。
在调用 getsockopt() 之前,需要初始化 olen 为选项值的最大可能长度(即 sizeof(int))。
getsockopt() 是一个标准的 POSIX 函数,用于获取套接字选项的值。olen 是输入输出参数,表示选项值的大小。
确保 getsockopt() 调用时能够正确处理选项值的长度,避免越界或错误。
2. if (getsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF, (void *) &ls[i].rcvbuf, &olen) == -1)
调用 getsockopt() 获取监听套接字的接收缓冲区大小(SO_RCVBUF),并检查调用是否成功。
如果调用失败(返回 -1),记录警告日志并将接收缓冲区大小设置为默认值 -1。# 逻辑思路接收缓冲区是操作系统内核为每个套接字维护的一块内存区域,用于临时存储从网络接收到的数据。
参数说明:
ls[i].fd:监听套接字的文件描述符。
SOL_SOCKET:指定选项的协议级别,这里表示通用套接字选项。
SO_RCVBUF:指定要获取的选项,表示接收缓冲区的大小。
(void *) &ls[i].rcvbuf:指向存储选项值的缓冲区,ls[i].rcvbuf 将保存接收缓冲区的大小。
&olen:输入输出参数,表示选项值的大小。调用完成后,olen 会被更新为实际选项值的长度。意图
获取监听套接字的接收缓冲区大小,以便后续根据缓冲区大小进行优化或调试。
如果调用失败,记录警告日志,并将接收缓冲区大小设置为默认值 -1,表示无法获取该值。
3. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, ...)
记录一条日志,说明 getsockopt(SO_RCVBUF) 调用失败的原因。
日志级别为 NGX_LOG_ALERT,表示这是一个警告级别的错误。
4. ls[i].rcvbuf = -1;
将监听套接字的接收缓冲区大小设置为 -1。
如果 getsockopt(SO_RCVBUF) 调用失败,则无法获取接收缓冲区的实际大小。
将 ls[i].rcvbuf 设置为 -1,表示该值无效或未知。意图
提供一个默认值,避免后续代码因未初始化的变量而引发问题。
明确标记该值不可用,便于后续逻辑判断。
总结
1. olen = sizeof(int);:
初始化选项值的长度,确保 getsockopt() 调用时能够正确处理。2. if (getsockopt(...)):
获取监听套接字的接收缓冲区大小(SO_RCVBUF)。
如果调用失败,记录警告日志并将接收缓冲区大小设置为默认值 -1。3. ngx_log_error(...):
记录详细的警告日志,说明 getsockopt(SO_RCVBUF) 调用失败的原因。4. ls[i].rcvbuf = -1;:
将接收缓冲区大小设置为默认值 -1,表示无法获取该值。
olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF, (void *) &ls[i].sndbuf, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_SNDBUF) %V failed, ignored", &ls[i].addr_text); ls[i].sndbuf = -1; }
1. olen = sizeof(int);:
初始化选项值的长度,确保 getsockopt() 调用时能够正确处理。2. if (getsockopt(...)):
获取监听套接字的发送缓冲区大小(SO_SNDBUF)。
如果调用失败,记录警告日志并将发送缓冲区大小设置为默认值 -1。3. ngx_log_error(...):
记录详细的警告日志,说明 getsockopt(SO_SNDBUF) 调用失败的原因。4. ls[i].sndbuf = -1;:
将发送缓冲区大小设置为默认值 -1,表示无法获取该值。
#if (NGX_HAVE_REUSEPORT) reuseport = 0; olen = sizeof(int); #ifdef SO_REUSEPORT_LB if (getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT_LB, (void *) &reuseport, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_REUSEPORT_LB) %V failed, ignored", &ls[i].addr_text); } else { ls[i].reuseport = reuseport ? 1 : 0; } #else if (getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT, (void *) &reuseport, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_REUSEPORT) %V failed, ignored", &ls[i].addr_text); } else { ls[i].reuseport = reuseport ? 1 : 0; } #endif #endif if (ls[i].type != SOCK_STREAM) { continue; }
1. #if (NGX_HAVE_REUSEPORT)
这是一个条件编译指令,用于检查当前系统是否支持 端口复用 功能。
如果定义了 NGX_HAVE_REUSEPORT 宏,则说明系统支持端口复用功能,因此需要处理相关逻辑。
2. reuseport = 0;
初始化变量 reuseport 的值为 0。
reuseport 是一个整数类型的变量,用于存储端口复用选项的状态。
在调用 getsockopt() 之前,将 reuseport 初始化为默认值 0,表示端口复用未启用。
3. olen = sizeof(int);
# 作用
初始化变量 olen 的值为 sizeof(int)。
olen 是一个 socklen_t 类型的变量,表示 getsockopt() 函数中选项值的长度。
4. #ifdef SO_REUSEPORT_LB
检查是否定义了 SO_REUSEPORT_LB 宏。
SO_REUSEPORT_LB 是某些操作系统(如 FreeBSD)特有的套接字选项,用于实现负载均衡的端口复用。
如果定义了 SO_REUSEPORT_LB,则优先使用该选项。
否则,回退到通用的 SO_REUSEPORT 选项。
5. if (getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT_LB, ...)
调用 getsockopt() 获取监听套接字的 SO_REUSEPORT_LB 状态,并检查调用是否成功。
如果调用失败(返回 -1),记录警告日志并将 ls[i].reuseport 设置为默认值。
SO_REUSEPORT_LB:指定要获取的选项,表示负载均衡的端口复用状态。
(void *) &reuseport:指向存储选项值的缓冲区。
6. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, ...)
记录一条日志,说明 getsockopt(SO_REUSEPORT_LB) 调用失败的原因。
日志级别为 NGX_LOG_ALERT,表示这是一个警告级别的错误。
7. ls[i].reuseport = reuseport ? 1 : 0;
根据 reuseport 的值设置 ls[i].reuseport。
如果 reuseport 非零,则设置为 1,表示启用了端口复用;否则设置为 0。
ls[i].reuseport 是一个布尔标志位,表示该套接字是否启用了端口复用。
明确标记端口复用的状态,便于后续逻辑判断。
8. #else
如果未定义 SO_REUSEPORT_LB,则使用通用的 SO_REUSEPORT 选项。
SO_REUSEPORT 是 Linux 和其他类 Unix 系统支持的通用端口复用选项。
逻辑与 SO_REUSEPORT_LB 类似,只是选项名称不同。
9. if (ls[i].type != SOCK_STREAM) { continue; }
检查监听套接字的类型是否为 SOCK_STREAM。
如果不是 SOCK_STREAM(例如 SOCK_DGRAM),跳过当前套接字,继续处理下一个。
ls[i].type 表示监听套接字的类型。
Nginx 主要处理面向连接的流式套接字(SOCK_STREAM),因此忽略其他类型的套接字。意图
提高效率:跳过无效套接字,专注于处理有效的套接字。
避免对非流式套接字执行不必要的操作,减少资源浪费。
总结
1. #if (NGX_HAVE_REUSEPORT):
条件编译确保仅在支持端口复用的平台上编译相关代码。2. reuseport = 0;:
初始化端口复用状态变量。3. olen = sizeof(int);:
初始化选项值的长度。4. #ifdef SO_REUSEPORT_LB:
检查是否支持负载均衡的端口复用选项。5. getsockopt(...):
获取端口复用状态(SO_REUSEPORT_LB 或 SO_REUSEPORT)。6. ngx_log_error(...):
记录详细的警告日志。7. ls[i].reuseport = reuseport ? 1 : 0;:
设置端口复用状态。8. if (ls[i].type != SOCK_STREAM) { continue; }:
跳过非流式套接字。
#if (NGX_HAVE_TCP_FASTOPEN) olen = sizeof(int); if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN, (void *) &ls[i].fastopen, &olen) == -1) { err = ngx_socket_errno; if (err != NGX_EOPNOTSUPP && err != NGX_ENOPROTOOPT && err != NGX_EINVAL) { ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, "getsockopt(TCP_FASTOPEN) %V failed, ignored", &ls[i].addr_text); } ls[i].fastopen = -1; } #endif
1. #if (NGX_HAVE_TCP_FASTOPEN)
这是一个条件编译指令,用于检查当前系统是否支持 TCP 快速打开(TCP Fast Open, TFO) 功能。
如果定义了 NGX_HAVE_TCP_FASTOPEN 宏,则说明系统支持 TCP 快速打开功能,因此需要处理相关逻辑。
2. olen = sizeof(int);
初始化变量 olen 的值为 sizeof(int)。
3. if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN, ...)
调用 getsockopt() 获取监听套接字的 TCP 快速打开状态,并检查调用是否成功。
如果调用失败(返回 -1),记录警告日志并将 ls[i].fastopen 设置为默认值 -1。
IPPROTO_TCP:指定选项的协议级别,这里表示 TCP 协议。
TCP_FASTOPEN:指定要获取的选项,表示 TCP 快速打开的状态。
(void *) &ls[i].fastopen:指向存储选项值的缓冲区,ls[i].fastopen 将保存快速打开的队列长度。
获取监听套接字的 TCP 快速打开状态,以便后续根据状态进行优化或调试。
如果调用失败,记录警告日志,并将状态设置为默认值。
4. err = ngx_socket_errno;
获取 getsockopt() 调用失败时的错误码,并将其存储到变量 err 中。
ngx_socket_errno 是一个宏,表示当前线程的套接字错误码。定义在 src/os/unix/ngx_errno.h
#define ngx_socket_errno errno
错误码用于判断失败的具体原因,例如是否是因为功能不受支持。
5. if (err != NGX_EOPNOTSUPP && err != NGX_ENOPROTOOPT && err != NGX_EINVAL)
检查错误码是否属于特定的忽略范围。
如果错误码不是 NGX_EOPNOTSUPP、NGX_ENOPROTOOPT 或 NGX_EINVAL,则记录警告日志。
常见的错误码及其含义:
NGX_EOPNOTSUPP:表示操作不被支持。
NGX_ENOPROTOOPT:表示协议选项无效。
NGX_EINVAL:表示无效参数。
如果错误码不属于这些范围,则可能是其他问题,需要记录日志。
避免因常见的非致命错误(如功能不支持)而记录过多的日志。
仅对可能影响程序运行的错误进行记录。
6. ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, ...)
记录一条日志,说明 getsockopt(TCP_FASTOPEN) 调用失败的原因。
日志级别为 NGX_LOG_NOTICE,表示这是一个通知级别的日志。
7. ls[i].fastopen = -1;
将监听套接字的 TCP 快速打开状态设置为 -1。
如果 getsockopt(TCP_FASTOPEN) 调用失败,则无法获取快速打开的实际状态。
将 ls[i].fastopen 设置为 -1,表示该值无效或未知。
总结
1. #if (NGX_HAVE_TCP_FASTOPEN):
条件编译确保仅在支持 TCP 快速打开的平台上编译相关代码。2. olen = sizeof(int);:
初始化选项值的长度。3. if (getsockopt(...)):
获取监听套接字的 TCP 快速打开状态。4. err = ngx_socket_errno;:
获取调用失败时的错误码。5. if (err != ...) { ngx_log_error(...); }:
根据错误码判断是否需要记录日志。6. ls[i].fastopen = -1;:
设置 TCP 快速打开状态为默认值 -1。
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) ngx_memzero(&af, sizeof(struct accept_filter_arg)); olen = sizeof(struct accept_filter_arg); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, &olen) == -1) { err = ngx_socket_errno; if (err == NGX_EINVAL) { continue; } ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, "getsockopt(SO_ACCEPTFILTER) for %V failed, ignored", &ls[i].addr_text); continue; } if (olen < sizeof(struct accept_filter_arg) || af.af_name[0] == '\0') { continue; } ls[i].accept_filter = ngx_palloc(cycle->pool, 16); if (ls[i].accept_filter == NULL) { return NGX_ERROR; } (void) ngx_cpystrn((u_char *) ls[i].accept_filter, (u_char *) af.af_name, 16); #endif
1. #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
这是一个条件编译指令,用于检查当前系统是否支持 延迟接受(Deferred Accept) 和 Accept Filter 功能。
如果定义了 NGX_HAVE_DEFERRED_ACCEPT 宏,并且系统定义了 SO_ACCEPTFILTER,则说明支持 FreeBSD 的 Accept Filter 机制,因此需要处理相关逻辑。
2. ngx_memzero(&af, sizeof(struct accept_filter_arg));
将结构体 af 的内存清零。
af 是一个 struct accept_filter_arg 类型的变量,用于存储 Accept Filter 的相关信息。
清零是为了确保结构体中的所有字段都有明确的初始值,避免未初始化导致的未定义行为。
3. olen = sizeof(struct accept_filter_arg);
初始化变量 olen 的值为 sizeof(struct accept_filter_arg)。
4. if (getsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, &olen) == -1)
调用 getsockopt() 获取监听套接字的 Accept Filter 配置,并检查调用是否成功。
如果调用失败(返回 -1),根据错误码进行处理。
SO_ACCEPTFILTER:指定要获取的选项,表示 Accept Filter 的配置。
&af:指向存储选项值的缓冲区。
意图
获取监听套接字的 Accept Filter 配置,以便后续根据配置进行优化或调试。
如果调用失败,根据错误码判断是否需要记录日志或跳过该套接字。
5. err = ngx_socket_errno;
获取 getsockopt() 调用失败时的错误码,并将其存储到变量 err 中。
6. if (err == NGX_EINVAL) { continue; }
检查错误码是否为 NGX_EINVAL。
如果是,则跳过当前套接字,继续处理下一个。
NGX_EINVAL 表示无效参数,通常是由于套接字未启用 Accept Filter 或其他配置问题。
在这种情况下,无需记录日志,直接跳过即可。
7. ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, ...)
记录一条日志,说明 getsockopt(SO_ACCEPTFILTER) 调用失败的原因。
日志级别为 NGX_LOG_NOTICE,表示这是一个通知级别的日志。
8. continue;
跳过当前循环的剩余部分,继续处理下一个监听套接字。
如果当前套接字的 Accept Filter 配置无法获取,则不需要继续对其进行初始化或配置。
直接跳过当前迭代,进入下一次循环。
9. if (olen < sizeof(struct accept_filter_arg) || af.af_name[0] == '\0') { continue; }
检查 getsockopt() 返回的数据是否有效。
如果返回的数据长度不足,或者 af_name 字段为空,则跳过当前套接字。
olen < sizeof(struct accept_filter_arg):表示返回的数据长度小于预期,可能是数据截断或其他问题。
af.af_name[0] == '\0':表示 af_name 字段为空,说明未配置 Accept Filter。意图
确保获取的 Accept Filter 配置是有效的,避免后续逻辑处理无效数据。
10. ls[i].accept_filter = ngx_palloc(cycle->pool, 16);
为 ls[i].accept_filter 分配内存,用于存储 Accept Filter 的名称。
分配的内存大小为 16 字节,足够存储 Accept Filter 的名称
11. if (ls[i].accept_filter == NULL) { return NGX_ERROR; }
检查 ngx_palloc 是否成功分配了内存。
如果分配失败(返回 NULL),直接返回错误码 NGX_ERROR。
12. (void) ngx_cpystrn((u_char *) ls[i].accept_filter, (u_char *) af.af_name, 16);
将 af.af_name 的内容复制到 ls[i].accept_filter 中。
af.af_name 是 Accept Filter 的名称。
复制后的名称存储在 ls[i].accept_filter 中,便于后续使用。
总结
1. #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER):
条件编译确保仅在支持 Accept Filter 的平台上编译相关代码。2. ngx_memzero(&af, sizeof(struct accept_filter_arg));:
初始化结构体 af,确保其处于干净状态。3. olen = sizeof(struct accept_filter_arg);:
初始化选项值的长度。4. if (getsockopt(...)):
获取监听套接字的 Accept Filter 配置。5. err = ngx_socket_errno;:
获取调用失败时的错误码。6. if (err == NGX_EINVAL) { continue; }:
忽略无效参数错误。7. ngx_log_error(...):
记录详细的警告日志。8. continue;:
跳过无效套接字。9. if (olen < ... || af.af_name[0] == '\0') { continue; }:
检查返回的数据是否有效。10. ls[i].accept_filter = ngx_palloc(...);:
为 Accept Filter 名称分配内存。11. if (ls[i].accept_filter == NULL) { return NGX_ERROR; }:
检查内存分配是否成功。12. (void) ngx_cpystrn(...);:
安全地复制 Accept Filter 名称。
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) timeout = 0; olen = sizeof(int); if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, &olen) == -1) { err = ngx_socket_errno; if (err == NGX_EOPNOTSUPP) { continue; } ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, "getsockopt(TCP_DEFER_ACCEPT) for %V failed, ignored", &ls[i].addr_text); continue; } if (olen < sizeof(int) || timeout == 0) { continue; } ls[i].deferred_accept = 1; #endif } return NGX_OK; }
1. #if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
这是一个条件编译指令,用于检查当前系统是否支持 延迟接受(Deferred Accept) 功能。
如果定义了 NGX_HAVE_DEFERRED_ACCEPT 宏,并且系统定义了 TCP_DEFER_ACCEPT,则说明支持 Linux 的延迟接受机制,因此需要处理相关逻辑。
2. timeout = 0;
初始化变量 timeout 的值为 0。
timeout 是一个整数类型的变量,用于存储延迟接受的超时时间(以秒为单位)。
3. olen = sizeof(int);
初始化变量 olen 的值为 sizeof(int)。
4. if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, &olen) == -1)
调用 getsockopt() 获取监听套接字的延迟接受超时时间,并检查调用是否成功。
如果调用失败(返回 -1),根据错误码进行处理。
TCP_DEFER_ACCEPT:指定要获取的选项,表示延迟接受的超时时间。
&timeout:指向存储选项值的缓冲区。
意图
获取监听套接字的延迟接受超时时间,以便后续根据配置进行优化或调试。
如果调用失败,根据错误码判断是否需要记录日志或跳过该套接字。
5. err = ngx_socket_errno;
获取 getsockopt() 调用失败时的错误码,并将其存储到变量 err 中。
错误码用于判断失败的具体原因,例如是否是因为功能不受支持。
6. if (err == NGX_EOPNOTSUPP) { continue; }
检查错误码是否为 NGX_EOPNOTSUPP。
如果是,则跳过当前套接字,继续处理下一个。
NGX_EOPNOTSUPP 表示操作不被支持,通常是由于内核或系统不支持延迟接受功能。
在这种情况下,无需记录日志,直接跳过即可。
7. ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, ...)
记录一条日志,说明 getsockopt(TCP_DEFER_ACCEPT) 调用失败的原因。
日志级别为 NGX_LOG_NOTICE,表示这是一个通知级别的日志。
8. continue;
跳过当前循环的剩余部分,继续处理下一个监听套接字。
如果当前套接字的延迟接受配置无法获取,则不需要继续对其进行初始化或配置。
直接跳过当前迭代,进入下一次循环。
9. if (olen < sizeof(int) || timeout == 0) { continue; }
检查 getsockopt() 返回的数据是否有效。
如果返回的数据长度不足,或者 timeout 为 0,则跳过当前套接字。
olen < sizeof(int):表示返回的数据长度小于预期,可能是数据截断或其他问题。
timeout == 0:表示未启用延迟接受功能。
确保获取的延迟接受配置是有效的,避免后续逻辑处理无效数据。
10. ls[i].deferred_accept = 1;
将监听套接字的延迟接受标志设置为 1。
ls[i].deferred_accept 是一个布尔标志位,表示该套接字是否启用了延迟接受功能。
如果 timeout 大于 0,说明延迟接受功能已启用,因此将标志位设置为 1。
明确标记延迟接受的状态,便于后续逻辑判断。
11. return NGX_OK;
返回成功状态码 NGX_OK。
如果所有监听套接字的延迟接受配置都处理完毕,则返回成功。
提供明确的返回值,便于调用者判断函数执行结果。
总结
1. #if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT):
条件编译确保仅在支持延迟接受的平台上编译相关代码。2. timeout = 0;:
初始化延迟接受超时时间变量。3. olen = sizeof(int);:
初始化选项值的长度。4. if (getsockopt(...)):
获取监听套接字的延迟接受超时时间。5. err = ngx_socket_errno;:
获取调用失败时的错误码。6. if (err == NGX_EOPNOTSUPP) { continue; }:
忽略操作不被支持的错误。7. ngx_log_error(...):
记录详细的警告日志。8. continue;:
跳过无效套接字。9. if (olen < ... || timeout == 0) { continue; }:
检查返回的数据是否有效。10. ls[i].deferred_accept = 1;:
设置延迟接受标志。11. return NGX_OK;:
返回成功状态码。