文章目录
http 模块解析 listen 配置
在 启动流程 这篇文章中,我提到 ngx_init_cycle
函数最后会调用 ngx_open_listening_sockets
函数打开所有监听的端口,会创建所有的 listening socket 。实际上,nginx 将需要创建的 listening socket 用 ngx_listening_t
结构体表示。查看 ngx_open_listening_sockets
函数代码我们就知道,它会遍历 cycle 中所有的 ngx_listening_t
结构,依次调用 socket、bind、listen 等系统调用完成打开端口的操作。
在 http 模块配置解析 这篇文章中,我提到处理 http{} 配置的函数 ngx_http_block
最后会调用 ngx_http_optimize_servers
函数创建 ngx_listening_t
结构。
nginx 创建 ngx_listening_t
结构不是一次性创建的,而是由 http 模块先创建一些辅助的结构 ngx_http_listen_opt_t
,然后由这个结构创建 ngx_listening_t
结构。而 ngx_http_listen_opt_t
结构是 ngx_http_core_module
在处理 listen 配置时创建的。所以我们先来分析 listen 配置的处理函数 ngx_http_core_listen
。
这篇文章就让我们看下创建 ngx_listening_t
结构的完整流程。为了便于说明代码,我们使用一个如下的配置文件:
events {
}
http {
server {
listen 80;
location / {
}
}
server {
listen 172.16.1.2:80;
location / {
}
}
server {
listen 172.16.1.3:8080;
location / {
}
}
}
这个配置文件使得 nginx 监听 0.0.0.0:80,172.16.1.2:80,172.16.1.3:8080。
ngx_http_core_listen
ngx_http_core_listen
函数的流程中涉及到的数据结构有好几个,我画了一张图,显示的就是这个函数处理上面的配置时创建的一些数据结构:
每个监听的 http 端口由 ngx_http_conf_port_t
结构表示。
每个监听的 IP地址和端口用 ngx_http_conf_addr_t
结构表示。其中的 opt 成员是 ngx_http_listen_opt_t
结构体,里面保存了监听的 IP地址和端口号。servers 成员是 ngx_http_core_srv_conf_t
结构体数组,对应这个 listen 配置所在的 server{} 配置。
我们给出的配置文件中, nginx 监听 0.0.0.0:80,172.16.1.2:80,172.16.1.3:8080,每个监听的地址属于一个 server{}。所以会创建 2 个 ngx_http_conf_port_t
结构,3 个 ngx_http_conf_addr_t
结构。
接下来看 ngx_http_core_listen
的代码就容易了。以下是解析 listen 80; 这个配置的流程:
cscf->listen = 1; /* server{} 有 listen 配置指令 */
value = cf->args->elts;
ngx_memzero(&u, sizeof(ngx_url_t));
u.url = value[1];
u.listen = 1;
u.default_port = 80;
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
if (u.err) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%s in \"%V\" of the \"listen\" directive",
u.err, &u.url);
}
return NGX_CONF_ERROR;
}
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
ngx_memcpy(&lsopt.sockaddr.sockaddr, &u.sockaddr, u.socklen);
lsopt.socklen = u.socklen;
lsopt.backlog = NGX_LISTEN_BACKLOG;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;
/* listen 80; 或 listen *; 或 listen *:80 */
lsopt.wildcard = u.wildcard;
(void) ngx_sock_ntop(&lsopt.sockaddr.sockaddr, lsopt.socklen, lsopt.addr,
NGX_SOCKADDR_STRLEN, 1);
它首先调用 ngx_parse_url
函数解析第一个参数,解析出参数中的 IP地址(0.0.0.0)和端口号(80),然后将 IP地址和端口号复制到 ngx_http_listen_opt_t
结构的局部变量 lsopt 中。
之后处理 listen 配置后面的参数:
for (n = 2; n < cf->args->nelts; n++) {
if (ngx_strcmp(value[n].data, "default_server") == 0
|| ngx_strcmp(value[n].data, "default") == 0)
{
lsopt.default_server = 1;
continue;
}
if (ngx_strcmp(value[n].data, "bind") == 0) {
lsopt.set = 1;
lsopt.bind = 1;
continue;
}
/* 省略部分代码 */
}
处理完所有的参数后它调用 ngx_http_add_listen
函数:
if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
return NGX_CONF_OK;
}
ngx_http_add_listen
ngx_http_add_listen
函数代码如下:
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
if (cmcf->ports == NULL) {
cmcf->ports = ngx_array_create(cf->temp_pool, 2,
sizeof(ngx_http_conf_port_t));
if (cmcf->ports == NULL) {
return NGX_ERROR;
}
}
sa = &lsopt->sockaddr.sockaddr;
p = ngx_inet_get_port(sa);
port = cmcf->ports->elts;
for (i = 0; i < cmcf->ports->nelts; i++) {
if (p != port[i].port || sa->sa_family != port[i].family) {
continue;
}
/* a port is already in the port list */
return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);
}
/* add a port to the port list */
port = ngx_array_push(cmcf->ports);
if (port == NULL) {
return NGX_ERROR;
}
port->family = sa->sa_family;
port->port = p;
port->addrs.elts = NULL;
return ngx_http_add_address(cf, cscf, port, lsopt);
}
它获取在处理 http{} 配置时创建的 ngx_http_core_main_conf_t
结构。由于是第一次进入函数,它先初始化 ports 数组。然后创建一个 ngx_http_conf_port_t
结构,赋值 port 成员的值为 80 ,将它加入数组中。最后调用 ngx_http_add_address
函数。
ngx_http_add_address
ngx_http_add_address
函数第一次进入时会初始化 addrs 数组,创建一个 ngx_http_conf_addr_t
结构,赋值 opt 成员,最后调用 ngx_http_add_server
函数:
if (port->addrs.elts == NULL) {
if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
sizeof(ngx_http_conf_addr_t))
!= NGX_OK)
{
return NGX_ERROR;
}
}
addr = ngx_array_push(&port->addrs);
if (addr == NULL) {
return NGX_ERROR;
}
addr->opt = *lsopt;
addr->hash.buckets = NULL;
addr->hash.size = 0;
addr->wc_head = NULL;
addr->wc_tail = NULL;
addr->default_server = cscf;
addr->servers.elts = NULL;
return ngx_http_add_server(cf, cscf, addr);
ngx_http_add_server
ngx_http_add_server
函数的套路也是一样,第一次进入时初始化 servers 数组,将 ngx_http_core_srv_conf_t
结构体加入其中:
if (addr->servers.elts == NULL) {
if (ngx_array_init(&addr->servers, cf->temp_pool, 4,
sizeof(ngx_http_core_srv_conf_t *))
!= NGX_OK)
{
return NGX_ERROR;
}
} else {
server = addr->servers.elts;
/* 遍历相同的端口和IP地址的 server{} 配置结构体 */
for (i = 0; i < addr->servers.nelts; i++) {
if (server[i] == cscf) {
/* server{}下有两条重复的 listen 指令,报错 */
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"a duplicate listen %s", addr->opt.addr);
return NGX_ERROR;
}
}
}
server = ngx_array_push(&addr->servers);
if (server == NULL) {
return NGX_ERROR;
}
*server = cscf;
在解析 listen 172.16.1.2:80; 这条配置时,ngx_http_core_listen
函数还是调用 ngx_http_add_listen
函数,在 ngx_http_add_listen
函数中由于 cmcf->ports 数组中已经存在 80 端口,所以它会调用 ngx_http_add_addresses
函数。
这个函数会遍历 port->addrs 数组,如果发现监听的 IP 地址已经在其中,则调用 ngx_http_add_server
函数将 ngx_http_core_srv_conf_t
结构体加入addr->servers 数组中。如果监听的 IP 地址是新的,则调用 ngx_http_add_address
函数。
在解析 listen 172.16.1.3:8080; 这条配置时,和解析 listen 80; 配置的流程一样。最终就创建了图中所示的那些结构。
ngx_http_optimize_servers
在解析完 http{} 配置后,ngx_http_block
调用 ngx_http_optimize_servers
函数。
这个函数遍历 ports 数组每一项,将每一项包含的 ngx_http_conf_addr_t
结构体数组排序,使得通配匹配的地址排在后面。在我们的例子中,处理 80 端口时,排序后的地址为 172.16.1.2,0.0.0.0。然后调用 ngx_http_init_listening
函数。
ngx_http_init_listening
这个函数遍历所有的 ngx_http_conf_addr_t
结构,调用 ngx_http_add_listening
函数 。
ngx_http_add_listening
ngx_http_add_listening
调用 ngx_create_listening
函数创建 ngx_listening_t
结构,将它加入 cycle 中的 listening 成员中。它的代码如下:
ls = ngx_create_listening(cf, &addr->opt.sockaddr.sockaddr,
addr->opt.socklen);
if (ls == NULL) {
NGX_LOG_CALL_LEVEL_DEC(cf->log);
return NULL;
}
ls->addr_ntop = 1;
ls->handler = ngx_http_init_connection; /* 重要 */
它将 ngx_listening_t
结构中的 handler 成员赋值为 ngx_http_init_connection
函数。这样,当 listening socket 上有读事件时,ngx_event_accept
函数在建立连接后就会调用 ngx_http_init_connection
函数处理。
ngx_http_init_listening
创建了 ngx_listening_t
结构后,ngx_http_init_listening
分配一个 ngx_http_port_t
结构,使 ngx_listening_t
结构中的 servers 成员指向它。然后调用 ngx_http_add_addrs
函数为每个监听的IP地址创建一个 ngx_http_in_addr_t
结构。
虽然我们配置文件中配置监听 0.0.0.0:80,172.16.1.2:80,172.16.1.3:8080。但实际上 ngx_http_init_listening
函数只创建了两个 ngx_listening_t
结构,最终只会有两个 listening socket。这是因为只要有一个端口上绑定了通配地址,就只需要创建一个绑定通配地址的 listening socket。
最终 ngx_http_optimize_servers
函数创建的一些数据结构的关系如上图所示。