//监听的配置信息,在ngx_http_core_main_conf_t结构的ports数组中将会保存所有的端口监听配置信息
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;
在nginx的http部分的核心模块ngx_http_core_module的srv_conf配置结构ngx_http_core_main_conf_t中,有一个ports数组,其就是用来存储上述结构体的,nginx将会建立这个数组然后来创建监听socket。
//地址结构
typedef struct {
ngx_http_listen_opt_t opt; //监听的配置结构
//一些相关的hash变量,例如servername与ngx_http_core_srv_conf_t
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; //这个地址的默认server
ngx_array_t servers; /* array of ngx_http_core_srv_conf_t */ //该地址的所有对应的server
} ngx_http_conf_addr_t;
在ngx_http_conf_port_t结构的最后一个域addrs是一个数组,其保存的就是上述结构,
//监听的配置结构
typedef struct {
union {
struct sockaddr sockaddr; //通用套接字地质结构
struct sockaddr_in sockaddr_in; //网际套接字地质结构
#if (NGX_HAVE_INET6)
struct sockaddr_in6 sockaddr_in6;
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
struct sockaddr_un sockaddr_un;
#endif
u_char sockaddr_data[NGX_SOCKADDRLEN];
} u;
socklen_t socklen; //地址结构长度
unsigned set:1;
unsigned default_server:1;
unsigned bind:1; //绑定标志
unsigned wildcard:1;
#if (NGX_HTTP_SSL)
unsigned ssl:1;
#endif
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
unsigned ipv6only:2;
#endif
unsigned so_keepalive:2;
int backlog;
int rcvbuf;
int sndbuf;
#if (NGX_HAVE_SETFIB)
int setfib;
#endif
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
int tcp_keepidle;
int tcp_keepintvl;
int tcp_keepcnt;
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
char *accept_filter;
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
ngx_uint_t deferred_accept;
#endif
u_char addr[NGX_SOCKADDR_STRLEN + 1];
} ngx_http_listen_opt_t;
这个对应的是地址结构ngx_http_conf_addr_t的opt域。
好了,看完这些重要的配置结构,我们可以开始着手看如何初始化http监听了,我们知道在server块命令中,有两个重要的命令,server_name与listen,server_name命令用来实现虚拟主机的功能,用来设置每个server块的虚拟主机名。listen命令则用来设置监听socket的信息。
我们先来看server_name的回调函数ngx_http_core_server:
value = cf->args->elts; //获取解析出来的参数
for (i = 1; i < cf->args->nelts; i++) {
ch = value[i].data[0];
if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.'))
|| (ch == '.' && value[i].len < 2))
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"server name \"%V\" is invalid", &value[i]);
return NGX_CONF_ERROR;
}
if (ngx_strchr(value[i].data, '/')) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"server name \"%V\" has suspicious symbols",
&value[i]);
}
sn = ngx_array_push(&cscf->server_names); //相当于是在srv_conf 的server_names数组中分配一个元素的空间,用来保存当前的server_name
if (sn == NULL) {
return NGX_CONF_ERROR;
}
sn->server = cscf;//将当前的server_name的所属srv_conf设置
if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) {
sn->name = cf->cycle->hostname;
} else {
sn->name = value[i]; //赋值
}
上面是截取的主要代码,说白了就是根据解析出来的参数创建一个ngx_http_server_name_t结构,然后将其压入到当前server块命令创建的ngx_http_core_srv_conf_t结构的server_names数组当中。
嗯,接下来看listen命令的回调函数ngx_http_core_listen(这个就比较重要了):
ngx_http_core_srv_conf_t *cscf = conf; //获取当前的srv_conf结构,这个ngx_http_core_srv_conf_t为当前server自己创建的ngx_http_core_srv_conf_t
ngx_str_t *value, size;
ngx_url_t u; //url结构
ngx_uint_t n;
ngx_http_listen_opt_t lsopt; //用来存储监听套接字的配置信息
cscf->listen = 1; //表示当前的server配置已经进行了listen配置,如果不listen配置的话,那么会安排默认的端口监听
value = cf->args->elts; //获取解析出来的参数
ngx_memzero(&u, sizeof(ngx_url_t));
u.url = value[1]; //获取传进来的地址(ip+port)
u.listen = 1;
u.default_port = 80; //默认端口
if (ngx_parse_url(cf->pool, &u) != NGX_OK) { //相当于是对u进行初始化,比如说ip地址,端口号等
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;
}
上面的代码首先是获取当前server块命令创建的ngx_http_core_srv_conf_t结构,然后用listen后面的参数(一般情况下是ip+端口号的形式)来初始化url结构的url参数,另外还创建了一个监听配置信息ngx_http_listen_opt_t结构。然后还调用了ngx_parse_url函数,通过ip地址,端口号等将url结构最终转化为socket地址结构等。
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
ngx_memcpy(&lsopt.u.sockaddr, u.sockaddr, u.socklen); //为lsopt的地址结构赋值
//初始化监听套接字的配置
lsopt.socklen = u.socklen; //socket地址结构的长度
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结构,接下来的代码还有一些对其的初始化,但是没什么意思,就不贴出来了,最后比较重要的一段代码是:
if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) { //加入监听套接字,将会创建ngx_http_conf_port_t结构,并将其放入到ngx_http_core_main_conf_t的ports数组当中
return NGX_CONF_OK;
}
通过调用ngx_http_add_listen函数,将会创建ngx_http_conf_port_t结构,并且还会把它保存到ngx_http_core_main_conf_t结构的ports数组当中,这样nginx就能通过这个数组来创建监听了。
好了接下来看ngx_http_add_listen函数吧。
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); //获取ngx_http_core_module的main_conf 配置结构
//初始化ngx_http_core_module的ports数组
if (cmcf->ports == NULL) { //如果其ports的数组还没有初始化
cmcf->ports = ngx_array_create(cf->temp_pool, 2,
sizeof(ngx_http_conf_port_t));
if (cmcf->ports == NULL) {
return NGX_ERROR;
}
}
首先是获取ngx_http_core_main_conf_t结构,这里需要注意的是,前面的文章我们已经说过了,虽然在server命令快中也会创建自己的ngx_http_conf_ctx_t结构,但是它们所指向的main_conf 都是统一的,因为这里的
ngx_http_core_main_conf_t结构其实也是唯一的。接下来还要判断当前期的ports数组是否为空,如果为空的话那么还要初始化这个数组。
//获取网际地址结构
sa = &lsopt->u.sockaddr;
//判断地址结构的类型
switch (sa->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = &lsopt->u.sockaddr_in6;
p = sin6->sin6_port;
break;
#endif
#if (NGX_HAVE_UNIX_DOMAIN)
case AF_UNIX:
p = 0;
break;
#endif
default: /* AF_INET */
sin = &lsopt->u.sockaddr_in; //获取IPV4地址结构
p = sin->sin_port; //获取端口号
break;
}
接下来的代码是从lsopt中获取通用套接字地址结构,然后判断类型,并获取最终的IPV4地址结构以及端口号。
port = cmcf->ports->elts;
//遍历ports数组,看要添加的端口号信息是否存在,如果已经存在的话,调用ngx_http_add_addresses函数将相应的地址信息加入到port上就可以的
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 */
//如果端口的信息已经存在于ports数组当中了,那么只需要添加一个地址结构就可以了
return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);
}
接下来是遍历
ngx_http_core_main_conf_t的ports数组中的所有ngx_http_conf_port_t结构,看是否已经有相同的端口的信息了,如果有的话,那么只需要调用ngx_http_add_addresses函数来在当前ngx_http_conf_port_t结构中添加一个地址信息就可以了。如果没有相同的端口的信息的话,那么就需要以下的代码在ports数组中添加一个ngx_http_conf_port_t结构。
//表示要添加的端口信息并没有存在,那么需要在ports数组中加入一个元素,并进行初始化,并还要添加地址信息
port = ngx_array_push(cmcf->ports);
if (port == NULL) {
return NGX_ERROR;
}
//相当于是为刚刚压入数组的元素赋值
port->family = sa->sa_family;
port->port = p; //端口号
port->addrs.elts = NULL; //将addres数组置空
//添加地址信息
return ngx_http_add_address(cf, cscf, port, lsopt); //为该端口添加地址结构
上述代码就是用来添加一个
ngx_http_conf_port_t结构,然后最后依然要调用ngx_http_add_address为其添加地址结构。嗯,接下来我们先来看ngx_http_add_addresses函数:
* 在port的addr数组中已经存在该地址时,直接将ngx_http_core_srv_conf_t
* 结构添加到到addr对应的servers数组中。
*/
for (i = 0; i < port->addrs.nelts; i++) {
//比较地址是否相同
if (ngx_memcmp(p, addr[i].opt.u.sockaddr_data + off, len) != 0) {
continue;
}
/* the address is already in the address list */
//将当前的ngx_http_core_srv_conf_t直接压入到addr的servers数组中就行了
if (ngx_http_add_server(cf, cscf, &addr[i]) != NGX_OK) {
return NGX_ERROR;
}
首先是循环ports的addrs数组,找到是否有地址相同的,如果有的话,那么就好办了,只用调用ngx_http_add_server函数,将当前的srv_conf结构压入到addr的servers数组就可以了。当然如果最后找不到相同的地址的话,其实还是要调用ngx_http_add_address函数,在port的addrs数组添加一项。
接下来来看ngx_http_add_address函数吧:
/*
* add the server address, the server names and the server core module
* configurations to the port list
*/
//添加地址信息,该函数用于创建一个地址结构ngx_http_conf_addr_t,并将其加入到port的addrs数组当中,并初始化地址结构
static ngx_int_t
ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
ngx_http_conf_addr_t *addr; //创建一个地址配置结构
//如果当前的port配置结构的addrs数组为空,那么初始化它
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); //压入ngx_http_conf_port_t结构的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;
#if (NGX_PCRE)
addr->nregex = 0;
addr->regex = NULL;
#endif
addr->default_server = cscf; //设置默认的server
addr->servers.elts = NULL;
return ngx_http_add_server(cf, cscf, addr); //该函数用于将当前的ngx_http_core_srv_conf_t结构压入到地质结构addr的servers数组中,表示当前的地址结构增加一个server
}
该函数,一看代码应该就知道是什么意思了吧,说白了就是创建一个ngx_http_conf_addr_t地址结构,然后将其压入到当前port的addrs数组当中,然后当然还要对当前的地址结构进行一些初始化,最后调用ngx_http_add_server函数将当前的ngx_http_core_srv_conf_t压入到地址结构的servers数组当中就可以了。
嗯,上面写的很多了,我们来回顾一下监听初始化的解析配置文件的过程吧。
(1)调用listen命令的回调函数ngx_http_core_listen,根据解析配置文件获取的ip+port来创建监听套接字的配置结构ngx_http_listen_opt_t
(2)调用ngx_http_add_listen函数,判断当前ngx_http_core_main_conf_t结构中ports数组是否有相应端口的信息,如果已经有了的话,那么只需要调用ngx_http_add_addresses函数,在对应的ngx_http_conf_port_t结构中加入相应的地址信息就行了。如果没有的话那么就得创建一个ngx_http_conf_port_t结构,并且把其保存到ngx_http_core_main_conf_t的ports数组当中,然后再调用ngx_http_add_address函数为其添加地址信息。
(3)对于ngx_http_add_addresses以及ngx_http_add_address函数,其实说白了就是在ngx_http_conf_port_t结构的addrs数组中添加一个ngx_http_conf_addr_t结构,即地址结构,并且还会调用ngx_http_add_server函数为其配置虚拟主机的信息,nginx也会根据这个地址结构来创建监听socket。
最后,就是在ngx_http_core_main_conf_t结构中会有一个ports数组保存所有的ngx_http_conf_port_t结构,然后再ngx_http_conf_port_t结构中又会有一个addrs数组,保存端口对应的所有地址结构ngx_http_conf_addr_t(嗯,毕竟是服务器嘛,有几个网卡都是正常的)。接着对于一个ngx_http_conf_addr_t结构,又会有一个servers数组来保存其对应的所有虚拟主机的配置信息。
好了,监听部分的配置文件解析就差不多了,接下来可以开始具体如何创建监听结构,即ngx_listening_t结构了,在http命令的回调函数ngx_http_block最后会调用ngx_http_optimize_servers函数来完成这个过程。接下来我们就来看看这个函数吧。
//该函数相当于是遍历ngx_http_core_main_conf_t结构中的ports数组,然后创建监听,并还要将它们保存到cycle变量的listening数组当中去
static ngx_int_t
ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
ngx_array_t *ports)
{
ngx_uint_t p, a;
ngx_http_conf_port_t *port; //监听配置结构
ngx_http_conf_addr_t *addr; //地址配置结构
if (ports == NULL) {
return NGX_OK;
}
port = ports->elts;
//遍历ngx_http_core_main_conf_t结构中的ports数组中所有ngx_http_conf_port_t结构
for (p = 0; p < ports->nelts; p++) {
/**
* 将addrs排序,带通配符的地址排在后面
*/
ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);
/*
* check whether all name-based servers have the same
* configuration as a default server for given address:port
*/
addr = port[p].addrs.elts;
for (a = 0; a < port[p].addrs.nelts; a++) {
if (addr[a].servers.nelts > 1
#if (NGX_PCRE)
|| addr[a].default_server->captures
#endif
)
{
/**
* 初始addr(ngx_http_conf_addr_t)中的hash、wc_head和wc_tail哈希表。
* 这些哈希表以server name(虚拟主机名)为key,server块的ngx_http_core_srv_conf_t为
* value,用于在处理请求时,根据请求的host请求行快速找到处理该请求的server配置结构。
*/
if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
return NGX_ERROR;
}
}
}
//初始化监听
if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
函数还是相对比较简单的,基本上注释也都说的比较清楚了,函数还会调用ngx_http_init_listening函数来具体的根据每一个ngx_http_conf_port_t结构来创建监听,接下来我们看看
ngx_http_init_listening函数吧。
//遍历ngx_http_conf_port_t结构addrs数组中的所有ngx_http_conf_addr_t结构,并根据它来创建监听
while (i < last) {
if (bind_wildcard && !addr[i].opt.bind) {
i++;
continue;
}
//添加监听,即在cycle变量中添加一个ngx_listening_t结构
ls = ngx_http_add_listening(cf, &addr[i]);
if (ls == NULL) {
return NGX_ERROR;
}
hport = ngx_pcalloc(cf->pool, sizeof(ngx_http_port_t));
if (hport == NULL) {
return NGX_ERROR;
}
ls->servers = hport;
if (i == last - 1) {
hport->naddrs = last;
} else {
hport->naddrs = 1;
i = 0;
}
switch (ls->sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
if (ngx_http_add_addrs6(cf, hport, addr) != NGX_OK) {
return NGX_ERROR;
}
break;
#endif
default: /* AF_INET */
if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) {
return NGX_ERROR;
}
break;
}
addr++;
last--;
}
上述代码的主要内容就是通过遍历ngx_http_conf_port_t结构addrs数组中的所有ngx_http_conf_addr_t结构,并根据它来创建监听(调用ngx_http_add_listening函数),而且还要初始化ngx_listenting_t的servers域,用它来指明每一个监听socket的虚拟主机的信息。我们再来看看ngx_http_add_listening函数:
//该函数用于根据地址结构创建ngx_listening_t
static ngx_listening_t *
ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
{
ngx_listening_t *ls;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf;
//创建一个监听,该监听结构会直接保存在cycle变量的listening数组当中去
ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);
if (ls == NULL) {
return NULL;
}
ls->addr_ntop = 1;
ls->handler = ngx_http_init_connection; //listen监听的处理函数,这里表示用这个函数来预处理刚刚accept的连接分配的connection
cscf = addr->default_server;
ls->pool_size = cscf->connection_pool_size;
ls->post_accept_timeout = cscf->client_header_timeout;
clcf = cscf->ctx->loc_conf[ngx_http_core_module.ctx_index];
ls->logp = clcf->error_log;
ls->log.data = &ls->addr_text;
ls->log.handler = ngx_accept_log_error;
上述代码是截取的最主要的代码,说白了就是调用ngx_create_listening函数,在cycle的listening数组中添加一个ngx_listenting_t结构,然后还要初始化其的一些基本信息。并且还要将其的hander设置为ngx_http_init_connection函数,这个我们在前面讲事件循环的时候说到过,当监听端口获取一个连接,并为其分配connection之后,会调用这个handler来处理这个connection。
好了,这样子的话就创建了监听结构,并将其保存到了cycle中去了,然后http的监听socket初始化就完事了。至于最后是怎么打开socket的前面的文章已经说过了。
嗯,接下来的文章可以具体来分析http部分了。