在nginx源码分析(4)中,看到了nginx的事件模型,但其中没有介绍监听socket的初始化。而对于web server来说,需要通过监听socket来监听客户端的连接等。本篇将会具体介绍这方面的内容。还记得在前文介绍ngx_cycle_t结构时,它具有一个listening属性,是一个数组,存储所有监听socket,下面就来看看这些信息是什么时候添加的、以及如何初始化的。
1. 重要的数据结构
1. ngx_http_conf_port_t
监听端口配置信息,addrs是在该端口上所有监听地址的数组。
typedef struct {
ngx_int_t family;
in_port_t port;
ngx_array_t addrs; /* array of ngx_http_conf_addr_t */
} ngx_http_conf_port_t;
2. ngx_http_conf_addr_t
监听地址配置信息,包含了所有在该addr:port监听的所有server块的ngx_http_core_srv_conf_t结构,以及hash、wc_head和wc_tail这些hash结构,保存了以server name为key,ngx_http_core_srv_conf_t为value的哈希表,用于快速查找对应虚拟主机的配置信息。
typedef struct {
ngx_http_listen_opt_t opt;
ngx_hash_t hash;
ngx_hash_wildcard_t *wc_head;
ngx_hash_wildcard_t *wc_tail;
#if (NGX_PCRE)
ngx_uint_t nregex;
ngx_http_server_name_t *regex;
#endif
/* the default server configuration for this address:port */
ngx_http_core_srv_conf_t *default_server;
ngx_array_t servers; /* array of ngx_http_core_srv_conf_t */
} ngx_http_conf_addr_t;
2. 监听socket初始化
nginx把需要监听的socket用ngx_listening_t表示,存放在ngx_cycle_t的listening数组中。具体监听socket的初始化可以分为3个步骤:1. 解析配置文件、获取监听socket相关信息;2. 初始化监听socket;3. 打开并配置监听socket。下面我们分步骤来查看具体细节。
(1)解析配置文件,获取监听socket信息
用于设置监听socket的指令主要有两个:server_name和listen。server_name指令用于实现虚拟主机的功能,会设置每个server块的虚拟主机名,在处理请求时会根据请求行中的host来转发请求。而listen就是设置监听socket的信息。这两个指令都是ngx_http_core_module的,首先看一下server_name指令的回调函数ngx_http_core_server_name,这个函数完成的功能很简单就是将server_name指令指定的虚拟主机名添加到ngx_http_core_srv_conf_t的server_names数组中,以便在后面对整个web server支持的虚拟主机进行初始化。
1. ngx_http_core_server_name
static char *
ngx_http_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_srv_conf_t *cscf = conf;
// 局部变量声明
……
value = cf->args->elts;
for (i = 1; i < cf->args->nelts; i++) {
ch = value[i].data[0];
// 一些异常的处理
……
sn = ngx_array_push(&cscf->server_names); // 向server_names数组添加元素
if (sn == NULL) {
return NGX_CONF_ERROR;
}
#if (NGX_PCRE)
sn->regex = NULL;
#endif
sn->server = cscf;
if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) {
sn->name = cf->cycle->hostname;
} else {
sn->name = value[i];
}
/* 前缀不是~就不是正则表达式,需要将server name转换为小写 */
if (value[i].data[0] != '~') {
ngx_strlow(sn->name.data, sn->name.data, sn->name.len);
continue;
}
#if (NGX_PCRE)
// 处理server name是正则表达式的情况
……
#else
return NGX_CONF_ERROR;
#endif
}
return NGX_CONF_OK;
}
2. ngx_http_core_listen
下面看一下listen指令的回调函数ngx_http_core_listen。这个函数主要还解析listen指令中的socket配置选项,并保存这些值,在函数的最后会调用ngx_http_add_listen函数添加监听socket的信息。
cscf->listen = 1;
将core module的server config的listen置为1,表示该server块已经调用listen指令,设置了监听socket信息。如果listen等于0,即server块没有调用listen指令,后面会对监听信息进行默认初始化,比如监听的端口是80,地址是localhost等。
// 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;
}
这段代码意图很简单,就是解析listen指令中的url,ip地址和端口号信息。
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
ngx_memcpy(&lsopt.u.sockaddr, u.sockaddr, u.socklen);
lsopt.socklen = u.socklen;
lsopt.backlog = NGX_LISTEN_BACKLOG;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
lsopt.setfib = -1;
#endif
lsopt.wildcard = u.wildcard;
/* 将二进制的地址结构,转换为文本格式 */
(void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr,
NGX_SOCKADDR_STRLEN, 1);
ngx_http_listen_opt_t用于存储listen socket的配置信息,比如rcvbuf、sndbuf、backlog等,就是一些基本的socket选项。后面的大部分代码主要是处理listen指令指定的配置选项进行初始化,比如:default_server、rcvbuf、sndbuf、backlog等,代码太冗长,这里不粘贴。
if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
return NGX_CONF_OK;
}
在函数的最后部分调用了ngx_http_add_listen添加监听socket信息。在具体介绍这个函数实现之前,先来看一下nginx是如何存储监听socket的地址信息的。
在ngx_http_core_main_conf_t中有ports属性,保存nginx监听的所有端口的信息。ports是ngx_http_conf_port_t类型的数组,而每个ngx_http_conf_port_t结构又具有addrs属性,它存放了对应端口上要监听的地址。addrs是ngx_http_conf_addr_t类型的数组,ngx_http_conf_addr_t结构包含在addr:port上监听的虚拟主机名及对应的配置信息。
ngx_http_core_main_conf_t
|---> prots: 监听的端口号的数组
|---> ngx_http_conf_port_t:端口号的配置信息
|---> addrs:在该端口号上,监听的所有地址的数组
|---> ngx_http_conf_addr_t:地址配置信息,包含在该addr:port上的多个虚拟主机
|---> servers:在addr:port上的说个server块的配置信息ngx_http_core_srv_conf_t
| |---> ngx_http_core_srv_conf_t