nginx源码分析(5)——监听socket初始化
在nginx源碼分析(4)中,看到了nginx的事件模型,但其中沒(méi)有介紹監(jiān)聽(tīng)socket的初始化。而對(duì)于web server來(lái)說(shuō),需要通過(guò)監(jiān)聽(tīng)socket來(lái)監(jiān)聽(tīng)客戶端的連接等。本篇將會(huì)具體介紹這方面的內(nèi)容。還記得在前文介紹ngx_cycle_t結(jié)構(gòu)時(shí),它具有一個(gè)listening屬性,是一個(gè)數(shù)組,存儲(chǔ)所有監(jiān)聽(tīng)socket,下面就來(lái)看看這些信息是什么時(shí)候添加的、以及如何初始化的。
1. 重要的數(shù)據(jù)結(jié)構(gòu)
????????1. ngx_http_conf_port_t
????????監(jiān)聽(tīng)端口配置信息,addrs是在該端口上所有監(jiān)聽(tīng)地址的數(shù)組。
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
????????監(jiān)聽(tīng)地址配置信息,包含了所有在該addr:port監(jiān)聽(tīng)的所有server塊的ngx_http_core_srv_conf_t結(jié)構(gòu),以及hash、wc_head和wc_tail這些hash結(jié)構(gòu),保存了以server name為key,ngx_http_core_srv_conf_t為value的哈希表,用于快速查找對(duì)應(yīng)虛擬主機(jī)的配置信息。
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. 監(jiān)聽(tīng)socket初始化
????????nginx把需要監(jiān)聽(tīng)的socket用ngx_listening_t表示,存放在ngx_cycle_t的listening數(shù)組中。具體監(jiān)聽(tīng)socket的初始化可以分為3個(gè)步驟:1. 解析配置文件、獲取監(jiān)聽(tīng)socket相關(guān)信息;2. 初始化監(jiān)聽(tīng)socket;3. 打開(kāi)并配置監(jiān)聽(tīng)socket。下面我們分步驟來(lái)查看具體細(xì)節(jié)。
(1)解析配置文件,獲取監(jiān)聽(tīng)socket信息
????????用于設(shè)置監(jiān)聽(tīng)socket的指令主要有兩個(gè):server_name和listen。server_name指令用于實(shí)現(xiàn)虛擬主機(jī)的功能,會(huì)設(shè)置每個(gè)server塊的虛擬主機(jī)名,在處理請(qǐng)求時(shí)會(huì)根據(jù)請(qǐng)求行中的host來(lái)轉(zhuǎn)發(fā)請(qǐng)求。而listen就是設(shè)置監(jiān)聽(tīng)socket的信息。這兩個(gè)指令都是ngx_http_core_module的,首先看一下server_name指令的回調(diào)函數(shù)ngx_http_core_server_name,這個(gè)函數(shù)完成的功能很簡(jiǎn)單就是將server_name指令指定的虛擬主機(jī)名添加到ngx_http_core_srv_conf_t的server_names數(shù)組中,以便在后面對(duì)整個(gè)web server支持的虛擬主機(jī)進(jìn)行初始化。
????????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數(shù)組添加元素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];}/* 前綴不是~就不是正則表達(dá)式,需要將server name轉(zhuǎn)換為小寫 */if (value[i].data[0] != '~') {ngx_strlow(sn->name.data, sn->name.data, sn->name.len);continue;} #if (NGX_PCRE)// 處理server name是正則表達(dá)式的情況…… ? ? ? #elsereturn NGX_CONF_ERROR; #endif}return NGX_CONF_OK; }
????????2. ngx_http_core_listen
????????下面看一下listen指令的回調(diào)函數(shù)ngx_http_core_listen。這個(gè)函數(shù)主要還解析listen指令中的socket配置選項(xiàng),并保存這些值,在函數(shù)的最后會(huì)調(diào)用ngx_http_add_listen函數(shù)添加監(jiān)聽(tīng)socket的信息。
? ?
cscf->listen = 1;將core module的server config的listen置為1,表示該server塊已經(jīng)調(diào)用listen指令, 設(shè)置了監(jiān)聽(tīng)socket信息。如果listen等于0,即server塊沒(méi)有調(diào)用listen指令, 后面會(huì)對(duì)監(jiān)聽(tīng)信息進(jìn)行默認(rèn)初始化,比如監(jiān)聽(tīng)的端口是80,地址是localhost等。// listen指令的參數(shù)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;}
????????這段代碼意圖很簡(jiǎn)單,就是解析listen指令中的url,ip地址和端口號(hào)信息。
? ?
????????ngx_http_listen_opt_t用于存儲(chǔ)listen socket的配置信息,比如rcvbuf、sndbuf、backlog等,就是一些基本的socket選項(xiàng)。后面的大部分代碼主要是處理listen指令指定的配置選項(xiàng)進(jìn)行初始化,比如:default_server、rcvbuf、sndbuf、backlog等,代碼太冗長(zhǎng),這里不粘貼。
????????在函數(shù)的最后部分調(diào)用了ngx_http_add_listen添加監(jiān)聽(tīng)socket信息。在具體介紹這個(gè)函數(shù)實(shí)現(xiàn)之前,先來(lái)看一下nginx是如何存儲(chǔ)監(jiān)聽(tīng)socket的地址信息的。
在ngx_http_core_main_conf_t中有ports屬性,保存nginx監(jiān)聽(tīng)的所有端口的信息。ports是ngx_http_conf_port_t類型的數(shù)組,而每個(gè)ngx_http_conf_port_t結(jié)構(gòu)又具有addrs屬性,它存放了對(duì)應(yīng)端口上要監(jiān)聽(tīng)的地址。addrs是ngx_http_conf_addr_t類型的數(shù)組,ngx_http_conf_addr_t結(jié)構(gòu)包含在addr:port上監(jiān)聽(tīng)的虛擬主機(jī)名及對(duì)應(yīng)的配置信息。
ngx_http_core_main_conf_t
????|---> prots: 監(jiān)聽(tīng)的端口號(hào)的數(shù)組
????????????????|---> ngx_http_conf_port_t:端口號(hào)的配置信息
???????????????????????????????|---> addrs:在該端口號(hào)上,監(jiān)聽(tīng)的所有地址的數(shù)組
????????????????????????????????????????????|---> ngx_http_conf_addr_t:地址配置信息,包含在該addr:port上的多個(gè)虛擬主機(jī)
???????????????????????????????????????????????????????????|---> servers:在addr:port上的說(shuō)個(gè)server塊的配置信息ngx_http_core_srv_conf_t
???????????????????????????????????????????????????????????| ???????????|---> ngx_http_core_srv_conf_t
???????????????????????????????????????????????????????????|---> opt:ngx_http_listen_opt_t類型,監(jiān)聽(tīng)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就通過(guò)這種方式將監(jiān)聽(tīng)的地址信息和端口信息組織起來(lái),這些配置信息在后面回用于初始化ngx_cycle_t的listening屬性等。下面看一下具體實(shí)現(xiàn)。
????????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數(shù)組。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;}接下來(lái),獲取監(jiān)聽(tīng)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數(shù)組,查看新添加的端口信息是否已經(jīng)存在,如果該端口信息存在則調(diào)用ngx_http_add_addresses函數(shù)在對(duì)應(yīng)的端口結(jié)構(gòu)上添加地址信息。否則,在prots數(shù)組中添加一個(gè)元素,并初始化,然后調(diào)用ngx_http_add_address函數(shù)添加地址信息。
? ?
????????
????????下面先看一下ngx_http_add_addresses函數(shù)如何在存在對(duì)應(yīng)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;}
????????這段代碼是確定具體協(xié)議類型的地址信息在地址結(jié)構(gòu)中的偏移量和長(zhǎng)度,因?yàn)椴荒鼙容^整個(gè)地址結(jié)構(gòu)。
? ?
????????如果port對(duì)應(yīng)的addrs數(shù)組中已經(jīng)存在要添加的地址對(duì)應(yīng)的ngx_http_conf_addr_t結(jié)構(gòu),那么直接向addr添加server塊的配置信息。接下來(lái)的代碼處理port的addrs數(shù)組中不存在對(duì)應(yīng)地址的情況,只是很簡(jiǎn)單的調(diào)用ngx_http_add_address函數(shù),它的功能就是向port的addrs數(shù)組添加元素,具體看一下這個(gè)函數(shù)。
????????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;}}很簡(jiǎn)單,如果port的addrs數(shù)組沒(méi)有初始化,那么對(duì)其初始化。addr = ngx_array_push(&port->addrs);if (addr == NULL) {return NGX_ERROR;}接下來(lái)向addrs添加一個(gè)元素。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;?? ?// 對(duì)于此addr:port,該server block是默認(rèn)的addr->servers.elts = NULL;
????????對(duì)新添加的元素初始化,會(huì)將第一個(gè)監(jiān)聽(tīng)addr:port信息的server塊作為default server,后續(xù)添加的監(jiān)聽(tīng)addr:port的server塊通過(guò)listen指令,并包含default_server選項(xiàng)可以修改監(jiān)聽(tīng)addr:port的default server。
? ?
????????最后,調(diào)用ngx_http_add_server將該server塊的server config添加到addr的servers數(shù)組中,這個(gè)函數(shù)邏輯很簡(jiǎn)單,首先檢查是否存在重復(fù)的server config,如果存在則報(bào)錯(cuò),否則添加一個(gè)新的元素。
????????通過(guò)上面這些函數(shù)的調(diào)用完成了配置信息的存儲(chǔ),下面看一下ngx_listening_t的初始化。
(2)初始化監(jiān)聽(tīng)socket(ngx_listening_t)
????????在http模塊初始化中,我們介紹了在函數(shù)ngx_http_block函數(shù)中調(diào)用ngx_http_optimize_servers函數(shù)完成ngx_listening_t初始化,下面看一下這個(gè)函數(shù)的實(shí)現(xiàn)。
????????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(虛擬主機(jī)名)為key,server塊的ngx_http_core_srv_conf_t為* value,用于在處理請(qǐng)求時(shí),根據(jù)請(qǐng)求的host請(qǐng)求行快速找到處理該請(qǐng)求的server配置結(jié)構(gòu)。*/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;}}
????????這個(gè)函數(shù)就是遍歷所有的端口號(hào),將端口號(hào)對(duì)應(yīng)的地址結(jié)構(gòu)的hash、wc_head和wc_tail初始化,這個(gè)在初始化后面的ngx_listening_t的servers字段時(shí)會(huì)用到。然后調(diào)用ngx_http_init_listening函數(shù)完成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會(huì)用來(lái)保存虛擬主機(jī)的信息,在處理請(qǐng)求時(shí)會(huì)賦值給request* 用于進(jìn)行虛擬主機(jī)的匹配*/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數(shù)組,因?yàn)槊總€(gè)ngx_http_conf_addr_t有opt屬性,也就是ngx_listening_t的配置信息,這里會(huì)調(diào)用ngx_http_add_listening函數(shù)創(chuàng)建ngx_listening_t并添加到ngx_cycle_t中。ngx_http_add_addrs函數(shù)用于初始化ls->servers,這個(gè)屬性主要是存放該監(jiān)聽(tīng)socket對(duì)應(yīng)的虛擬主機(jī)的信息,在處理請(qǐng)求時(shí)根據(jù)請(qǐng)求行的host匹配,選擇對(duì)應(yīng)的一個(gè)server塊的ngx_http_core_srv_conf_t結(jié)構(gòu),這個(gè)結(jié)構(gòu)里存放了剛請(qǐng)求處理的全局配置信息。下面看一下ngx_http_add_listening函數(shù)。
????????3. ngx_http_add_listening
????????調(diào)用ngx_create_listening函數(shù)創(chuàng)建ngx_listening_t,這個(gè)函數(shù)的內(nèi)容在下面分析。
????????設(shè)置ngx_listening_t的handler,這個(gè)handler會(huì)在監(jiān)聽(tīng)到客戶端連接時(shí)被調(diào)用,具體就是在ngx_event_accept函數(shù)中,ngx_http_init_connection函數(shù)顧名思義,就是初始化這個(gè)新建的連接。后面的代碼就是根據(jù)addr的opt屬性初始化創(chuàng)建的ngx_listening_t結(jié)構(gòu)。下面看一下ngx_http_create_listengint函數(shù)。
????????4. ngx_http_create_listening
? ?
ls = ngx_array_push(&cf->cycle->listening);if (ls == NULL) {return NULL;}首先向ngx_cycle_t的listenging數(shù)組添加一個(gè)元素。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);配置地址信息,包括二進(jìn)制地址和文本格式地址。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
????????對(duì)屬性設(shè)置默認(rèn)值。
(3) ?打開(kāi)并配置監(jiān)聽(tīng)socket
????????在nginx啟動(dòng)過(guò)程中,介紹過(guò)在ngx_init_cycle函數(shù)中,會(huì)調(diào)用ngx_open_listening_sockets和ngx_configure_listening_sockets函數(shù)完成監(jiān)聽(tīng)socket的打開(kāi)和配置,下面具體看一下這兩函數(shù)。
????????1. ngx_open_listening_sockets
????????這個(gè)函數(shù)就是打開(kāi)socket,和一般socket編程一樣,就是調(diào)用socket、bind和listen,具體看一下注釋。
????????2. ngx_configure_listening_sockets
????????這個(gè)函數(shù)就是根據(jù)配置調(diào)用setsockopt函數(shù)設(shè)置socket選項(xiàng),比如接收緩沖區(qū)、發(fā)送緩沖區(qū)等,具體也沒(méi)有什么可講的。
????????通過(guò)這3個(gè)步驟就完成了監(jiān)聽(tīng)socket的初始化,接下來(lái)就會(huì)在ngx_event_process_init函數(shù)(ngx_event_core_moduel的process_init回調(diào)函數(shù),在創(chuàng)建完worker進(jìn)程后調(diào)用)中將這些監(jiān)聽(tīng)socket添加到事件循環(huán)中。
---------------------?
作者:chosen0ne?
來(lái)源:CSDN?
原文:https://blog.csdn.net/chosen0ne/article/details/7754608?
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)附上博文鏈接!
總結(jié)
以上是生活随笔為你收集整理的nginx源码分析(5)——监听socket初始化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux ngx listen的解析
- 下一篇: Nginx模块开发中使用PCRE正则表达