nginx源码分析(5)——监听socket初始化
在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; #endifsn->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是正则表达式的情况…… #elsereturn 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_http_listen_opt_t用于存储listen socket的配置信息,比如rcvbuf、sndbuf、backlog等,就是一些基本的socket选项。后面的大部分代码主要是处理listen指令指定的配置选项进行初始化,比如:default_server、rcvbuf、sndbuf、backlog等,代码太冗长,这里不粘贴。
在函数的最后部分调用了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
|---> opt:ngx_http_listen_opt_t类型,监听socket的配置信息
|---> hash:以server_name为key,ngx_http_core_srv_conf_t为value的hash表,并且server_name不含通配符。
|---> wc_head:同hash,server_name含前缀通配符。
|---> wc_tail:同hash,server_name含后缀通配符。
nginx就通过这种方式将监听的地址信息和端口信息组织起来,这些配置信息在后面回用于初始化ngx_cycle_t的listening属性等。下面看一下具体实现。
3. 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;}}初始化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; #endifdefault: /* AF_INET */sin = &lsopt->u.sockaddr_in;p = sin->sin_port;break;}接下来,获取监听socket的端口信息和地址信息。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);}
这段代码是遍历ports数组,查看新添加的端口信息是否已经存在,如果该端口信息存在则调用ngx_http_add_addresses函数在对应的端口结构上添加地址信息。否则,在prots数组中添加一个元素,并初始化,然后调用ngx_http_add_address函数添加地址信息。
下面先看一下ngx_http_add_addresses函数如何在存在对应port的情况下的添加地址信息。
4. ngx_http_add_addresses
sa = &lsopt->u.sockaddr;switch (sa->sa_family) { #if (NGX_HAVE_INET6)case AF_INET6:off = offsetof(struct sockaddr_in6, sin6_addr);len = 16;break; #endif #if (NGX_HAVE_UNIX_DOMAIN)case AF_UNIX:off = offsetof(struct sockaddr_un, sun_path);len = sizeof(saun->sun_path);break; #endifdefault: /* AF_INET */off = offsetof(struct sockaddr_in, sin_addr);len = 4;break;}
这段代码是确定具体协议类型的地址信息在地址结构中的偏移量和长度,因为不能比较整个地址结构。
如果port对应的addrs数组中已经存在要添加的地址对应的ngx_http_conf_addr_t结构,那么直接向addr添加server块的配置信息。接下来的代码处理port的addrs数组中不存在对应地址的情况,只是很简单的调用ngx_http_add_address函数,它的功能就是向port的addrs数组添加元素,具体看一下这个函数。
5. ngx_http_add_address
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;}}很简单,如果port的addrs数组没有初始化,那么对其初始化。addr = ngx_array_push(&port->addrs);if (addr == NULL) {return NGX_ERROR;}接下来向addrs添加一个元素。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; #endifaddr->default_server = cscf; // 对于此addr:port,该server block是默认的addr->servers.elts = NULL;
对新添加的元素初始化,会将第一个监听addr:port信息的server块作为default server,后续添加的监听addr:port的server块通过listen指令,并包含default_server选项可以修改监听addr:port的default server。
最后,调用ngx_http_add_server将该server块的server config添加到addr的servers数组中,这个函数逻辑很简单,首先检查是否存在重复的server config,如果存在则报错,否则添加一个新的元素。
通过上面这些函数的调用完成了配置信息的存储,下面看一下ngx_listening_t的初始化。
(2)初始化监听socket(ngx_listening_t)
在http模块初始化中,我们介绍了在函数ngx_http_block函数中调用ngx_http_optimize_servers函数完成ngx_listening_t初始化,下面看一下这个函数的实现。
1. ngx_http_optimize_servers
port = ports->elts;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* configuraiton 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;}}}/*** 初始化ngx_listening_t*/if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {return NGX_ERROR;}}
这个函数就是遍历所有的端口号,将端口号对应的地址结构的hash、wc_head和wc_tail初始化,这个在初始化后面的ngx_listening_t的servers字段时会用到。然后调用ngx_http_init_listening函数完成ngx_listening_t初始化。
2. ngx_http_init_listening
while (i < last) {if (bind_wildcard && !addr[i].opt.bind) {i++;continue;}/*** 添加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;}/*** servers会用来保存虚拟主机的信息,在处理请求时会赋值给request* 用于进行虚拟主机的匹配*/ls->servers = hport;if (i == last - 1) {hport->naddrs = last;} else {hport->naddrs = 1;i = 0;}/* 初始化ngx_http_virtual_names_t */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; #endifdefault: /* AF_INET */if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) {return NGX_ERROR;}break;}addr++;last--;}
遍历port的addrs数组,因为每个ngx_http_conf_addr_t有opt属性,也就是ngx_listening_t的配置信息,这里会调用ngx_http_add_listening函数创建ngx_listening_t并添加到ngx_cycle_t中。ngx_http_add_addrs函数用于初始化ls->servers,这个属性主要是存放该监听socket对应的虚拟主机的信息,在处理请求时根据请求行的host匹配,选择对应的一个server块的ngx_http_core_srv_conf_t结构,这个结构里存放了刚请求处理的全局配置信息。下面看一下ngx_http_add_listening函数。
3. ngx_http_add_listening
调用ngx_create_listening函数创建ngx_listening_t,这个函数的内容在下面分析。
设置ngx_listening_t的handler,这个handler会在监听到客户端连接时被调用,具体就是在ngx_event_accept函数中,ngx_http_init_connection函数顾名思义,就是初始化这个新建的连接。后面的代码就是根据addr的opt属性初始化创建的ngx_listening_t结构。下面看一下ngx_http_create_listengint函数。
4. ngx_http_create_listening
ls = ngx_array_push(&cf->cycle->listening);if (ls == NULL) {return NULL;}首先向ngx_cycle_t的listenging数组添加一个元素。ngx_memzero(ls, sizeof(ngx_listening_t));sa = ngx_palloc(cf->pool, socklen);if (sa == NULL) {return NULL;}ngx_memcpy(sa, sockaddr, socklen);ls->sockaddr = sa;ls->socklen = socklen;len = ngx_sock_ntop(sa, text, NGX_SOCKADDR_STRLEN, 1);ls->addr_text.len = len;ls->addr_text.data = ngx_pnalloc(cf->pool, len);if (ls->addr_text.data == NULL) {return NULL;}ngx_memcpy(ls->addr_text.data, text, len);配置地址信息,包括二进制地址和文本格式地址。ls->fd = (ngx_socket_t) -1;ls->type = SOCK_STREAM;ls->backlog = NGX_LISTEN_BACKLOG;ls->rcvbuf = -1;ls->sndbuf = -1;#if (NGX_HAVE_SETFIB)ls->setfib = -1; #endif
对属性设置默认值。
(3) 打开并配置监听socket
在nginx启动过程中,介绍过在ngx_init_cycle函数中,会调用ngx_open_listening_sockets和ngx_configure_listening_sockets函数完成监听socket的打开和配置,下面具体看一下这两函数。
1. ngx_open_listening_sockets
这个函数就是打开socket,和一般socket编程一样,就是调用socket、bind和listen,具体看一下注释。
2. ngx_configure_listening_sockets
这个函数就是根据配置调用setsockopt函数设置socket选项,比如接收缓冲区、发送缓冲区等,具体也没有什么可讲的。
通过这3个步骤就完成了监听socket的初始化,接下来就会在ngx_event_process_init函数(ngx_event_core_moduel的process_init回调函数,在创建完worker进程后调用)中将这些监听socket添加到事件循环中。
---------------------
作者:chosen0ne
来源:CSDN
原文:https://blog.csdn.net/chosen0ne/article/details/7754608
版权声明:本文为博主原创文章,转载请附上博文链接!
总结
以上是生活随笔为你收集整理的nginx源码分析(5)——监听socket初始化的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: linux ngx listen的解析
- 下一篇: nginx处理http(http变量篇)