在前面的幾篇文章中已經分析了master進程、work進程的初始化流程。但一直沒有分析監聽socket的創建流程,nginx服務器只有在創建socket, 綁定socet,監聽socket執行完成后,才能處理來自客戶端的連接。ngx_cycle_t結構中有一個listening成員,存放的就是所有監聽socket。接下來首先分析socket內部結構的維護,不同域名的管理,然后分析什么時候把監聽socket添加到listening數組中,最后分析何時創建監聽socket。
一、socket內部結構管理
static ngx_command_t ?ngx_http_core_commands[] =?
{{ ngx_string("listen"),NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,ngx_http_core_listen,NGX_HTTP_SRV_CONF_OFFSET,0,NULL },
}
????????在ngx_http_core_module模塊的命令列表中,listen命令的實現函數為ngx_http_core_listen。nginx.conf配置文件中,每一個server塊都可以監聽不同的端口,listen命令函數的作用就一個,就是為了維護socket的內部結構。如果直接分析源代碼,則不好表達,也不太好理解。還是以一個例子來說明socket內部結構的維護吧!
http
{#server1,監聽80端口,ip為192.168.100.1server{listen 192.168.100.1:80;server_name www.server1.com;}#server2,監聽80端口,ip位192.168.100.2server{listen 192.168.100.2:80;server_name www.server2.com;}#server3,監聽80端口,ip位192.168.100.1server{listen 192.168.100.1:80;server_name www.server3.com;}#server4,監聽90端口,ip位192.168.100.4server{listen 192.168.100.4:90;server_name www.server4.com;}
}
????????假設有這樣一個nginx.conf配置文件。192.168.100.1 ---192.168.100.2這兩個ip監聽同一個端口80, 而 192.168.100.4監聽端口90。nginx一共監聽了2個端口,則nginx維護的內部結構如下圖:
????????在這張圖中,192.168.100.1 ---192.168.100.2這兩個ip監聽端口80。192.168.100.1:80這個socket對應有www.server1.com; www.server3.com共兩個域名。?192.168.100.2:80這個socket對應有www.server2.com共一個域名。
????????192.168.100.4這個ip監聽90端口,對應的域名為www.server4.com
????????nginx維護這樣的結構是為了做什么? 假設nginx在192.168.100.1 :80這個socket收到來自客戶端的連接,http請求頭部的host=www.server3.com。則查找過程如下:
(1)先在ports數組中查找80端口對應的結構ngx_http_conf_port_t;
(2)接著查找ngx_http_conf_port_t中的addrs數組,從而獲取到192.168.100.1所在的ngx_http_conf_addr_t;?
(3)然后查找ngx_http_conf_addr_t結構中的servers數組,從而獲取得到www.server3.com域名所在的server塊。
????????從這個例子可以看出,即便來自同一個ip:port的客戶端連接,由于請求域名的不同,從而會獲取到不同的server塊進行處理。而listen命令的函數ngx_http_core_listen就是用來維護這樣的一種socket結構
//解析listen命令
static char * ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{cscf->listen = 1;?? ?//表示server塊配置了listen配置項value = cf->args->elts;u.url = value[1];u.listen = 1;u.default_port = 80;//解析listen的參數1,獲取監聽的ip地址以及端口信息ngx_parse_url(cf->pool, &u);ngx_memcpy(&lsopt.u.sockaddr, u.sockaddr, u.socklen);//給監聽選項結構賦值默認值lsopt.socklen = u.socklen;lsopt.backlog = NGX_LISTEN_BACKLOG;lsopt.rcvbuf = -1;lsopt.sndbuf = -1;//將struct sockaddr結構轉為點分十進制的ip地址格式,例如172.16.3.180(void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr,NGX_SOCKADDR_STRLEN, 1);//listen配置的參數保存到監聽選項結構for (n = 2; n < cf->args->nelts; n++){//設置默認的server塊if (ngx_strcmp(value[n].data, "default_server") == 0|| ngx_strcmp(value[n].data, "default") == 0){lsopt.default_server = 1;continue;}//設置監聽隊列大小if (ngx_strncmp(value[n].data, "backlog=", 8) == 0){lsopt.backlog = ngx_atoi(value[n].data + 8, value[n].len - 8);lsopt.set = 1;lsopt.bind = 1;if (lsopt.backlog == NGX_ERROR || lsopt.backlog == 0)?{ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"invalid backlog \"%V\"", &value[n]);return NGX_CONF_ERROR;}continue;}}//將server塊添加到監聽端口中,表示有多少個server塊在監聽同一個端口if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK)?{return NGX_CONF_OK;}return NGX_CONF_ERROR;
}
?
????????而ngx_parse_url函數是從listen配置項格式中提取到需要監聽的ip地址,端口號等信息。
//根據listen命令格式"listen ip:port"獲取ip, port,保存到u對應的成員中
ngx_int_t ngx_parse_url(ngx_pool_t *pool, ngx_url_t *u)
{u_char ?*p;p = u->url.data;//格式: listen unix:/var/run/nginx.sockif (ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0){return ngx_parse_unix_domain_url(pool, u);}if ((p[0] == ':' || p[0] == '/') && !u->listen)?{u->err = "invalid host";return NGX_ERROR;}//格式: listen [fe80::1];if (p[0] == '['){return ngx_parse_inet6_url(pool, u);}//根據url獲取ipv4格式的地址信息()return ngx_parse_inet_url(pool, u);
}
???????ngx_http_add_listen這個函數開始就是要創建圖中的ports數組內容了,使得nginx能監聽不同的端口。
//將server塊添加到監聽端口中,表示有多少個server塊在監聽同一個端口
ngx_int_t ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,ngx_http_listen_opt_t *lsopt)
{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));}sa = &lsopt->u.sockaddr;//獲取監聽端口switch (sa->sa_family){default: /* AF_INET */sin = &lsopt->u.sockaddr_in;p = sin->sin_port;break;}//查找是否存在相同的監聽端口,相同則只添加server塊port = cmcf->ports->elts;for (i = 0; i < cmcf->ports->nelts; i++)?{if (p != port[i].port || sa->sa_family != port[i].family)?{continue;}//查找到有多個ip監聽同一個端口時的處理邏輯return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);}//指向到此,說明沒有查找到相同的監聽端口,則需要創建一個端口port = ngx_array_push(cmcf->ports);port->family = sa->sa_family;port->port = p;port->addrs.elts = NULL;//如果有多個不同的ip監聽同一個端口,則需要把這些ip信息保持到ngx_http_conf_port_t結構中的addrs數組中return ngx_http_add_address(cf, cscf, port, lsopt);
}如果有多個不同的ip監聽同一個端口,則需要把這些ip信息保持到ngx_http_conf_port_t結構中的addrs數組中。ngx_http_add_address函數完成這個功能
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)
{if (port->addrs.elts == NULL)?{ngx_array_init(&port->addrs, cf->temp_pool, 4, sizeof(ngx_http_conf_addr_t)}//同一個監聽端口,但對于不同的ip邏輯,則需要創建一個ngx_http_conf_addr_t結構。//表示有多個ip地址監聽同一個端口addr = ngx_array_push(&port->addrs);//保存監聽端口的信息addr->opt = *lsopt;addr->hash.buckets = NULL;addr->hash.size = 0;addr->wc_head = NULL;addr->wc_tail = NULL;addr->default_server = cscf;?? ??? ?//多個server塊監聽同一個端口時,有一個默認的server塊addr->servers.elts = NULL;//將監聽同一個端口的server塊加入到server數組中return ngx_http_add_server(cf, cscf, addr);
}最終將將監聽ip:port的server保存到數組中,則是由ngx_http_add_server這個函數完成。
//將監聽同一個端口的server塊加入到server數組中
static ngx_int_t ngx_http_add_server(ngx_conf_t *cf,?ngx_http_core_srv_conf_t *cscf,ngx_http_conf_addr_t *addr)
{ngx_uint_t ? ? ? ? ? ? ? ? ?i;ngx_http_core_srv_conf_t ?**server;//監聽同一個端口的數組不存在,則創建if (addr->servers.elts == NULL){ngx_array_init(&addr->servers, cf->temp_pool, 4, sizeof(ngx_http_core_srv_conf_t *)}else?{//查找是否同一個server塊兩次調用listen監聽同一個端口//在同一個server塊中調用兩次listen監聽同一個端口是不允許的;例如://listen 80; listen 80;是非法的server = addr->servers.elts;for (i = 0; i < addr->servers.nelts; i++)?{if (server[i] == cscf)?{return NGX_ERROR;}}}//獲取一個server塊server = ngx_array_push(&addr->servers);if (server == NULL){return NGX_ERROR;}//保存server塊,表示這個server塊監聽ip:portsocket*server = cscf;
}
????????到此為止,nginx對于監聽socket維護的內部結構已經分析完成了。接下里分析下nginx服務器對不同域名的管理
二、域名管理
????????在解析http配置塊中,調用了ngx_http_optimize_servers函數,將把各個監聽端口下的所有域名加入到相應哈希表中(例如:普通哈希表,前置通配符哈希表,后置通配符哈希表)。這樣nginx在收到客戶端連接請求時,就可以直接根據http請求頭部host字段查找這些哈希表,從而獲取到server塊,由這個server塊處理這個http請求。
//開始解析http塊
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
//功能: 將所有監聽socket對應的服務器名加入到哈希表中。例如:有80?90兩個端口。則將80端口所有server塊下的
//所有服務器名加入到哈希表;將90端口所有server塊下的所有服務器名加入到哈希表if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK){return NGX_CONF_ERROR;}
}
????????ngx_http_optimize_servers這個函數負責將各個監聽端口下的所有域名加入到相應哈希表中;該函數同時也會創建一個監聽對象
//功能: 將所有監聽socket對應的服務器名加入到哈希表中。例如:有80?90兩個端口。則將80端口所有server塊下的
//所有服務器名加入到哈希表;將90端口所有server塊下的所有服務器名加入到哈希表
static ngx_int_t ngx_http_optimize_servers(ngx_conf_t *cf,?ngx_http_core_main_conf_t *cmcf, ngx_array_t *ports)
{//對于每一個監聽端口,都會有對應多個server塊監聽這個端口。這每一個server塊又可能有多少server名稱。//下面這個循環處理每一個端口,構成一個哈希表port = ports->elts;for (p = 0; p < ports->nelts; p++){//將把監聽同一個端口的所有ip信息排序ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);addr = port[p].addrs.elts;for (a = 0; a < port[p].addrs.nelts; a++)?{//處理監聽同一個端口的所有server塊,構成一個哈希表if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK)?{return NGX_ERROR;}}//創建ngx_listening_s對象,并用port數組給這個對象賦值if (ngx_http_init_listening(cf, &port[p]) != NGX_OK){return NGX_ERROR;}}
}
????????函數ngx_http_server_names將創建由所有域名構成的普通哈希表,前置通配符哈希表、后置通配符哈希表。這樣在收到來自客戶端的請求時,可以根據http頭部的host字段查找這些哈希表,進而獲取到server塊。
//創建服務名哈希表
static ngx_int_t ngx_http_server_names(ngx_conf_t *cf,?ngx_http_core_main_conf_t *cmcf,ngx_http_conf_addr_t *addr)
{//創建一個哈希數組if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK)?{goto failed;}cscfp = addr->servers.elts;//多個server塊監聽同一個端口for (s = 0; s < addr->servers.nelts; s++)?{name = cscfp[s]->server_names.elts;//每一個server塊又可能有多個服務器名for (n = 0; n < cscfp[s]->server_names.nelts; n++){//將服務器名加入到哈希數組中rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server,NGX_HASH_WILDCARD_KEY);}}//創建普通哈希表if (ha.keys.nelts)?{hash.hash = &addr->hash;hash.temp_pool = NULL;if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK)?{goto failed;}}//創建前置哈希表if (ha.dns_wc_head.nelts){ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts,sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts, ha.dns_wc_head.nelts);addr->wc_head = (ngx_hash_wildcard_t *) hash.hash;}//創建后置通配符哈希表if (ha.dns_wc_tail.nelts)?{ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts,ha.dns_wc_tail.nelts);addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash;}
}
?
三、監聽對象的創建
????????而ngx_http_init_listening函數將創建一個創建一個ngx_listening_t對象,并個這個對象的成員賦值。同時將創建后的這個對象存放到cf->cycle->listening這個監聽數組中保存。
static ngx_int_t ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port)
{while (i < last)?{//創建一個ngx_listening_t對象,并個這個對象的成員賦值。例如設置監聽回調ls = ngx_http_add_listening(cf, &addr[i]);if (ls == NULL){return NGX_ERROR;}}
}
????????ngx_http_add_listening函數創建監聽對象,并設置監聽的回調為ngx_http_init_connection。在監聽到客戶端的連接時,這個回調將被調用。
//創建一個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_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);ls->addr_ntop = 1;//監聽回調ls->handler = ngx_http_init_connection;//在有多少server塊監聽同一個端口時,使用默認塊的配置cscf = addr->default_server;ls->pool_size = cscf->connection_pool_size;ls->post_accept_timeout = cscf->client_header_timeout;return ls;
}
四、監聽socket
????????在函數ngx_init_cycle有這樣的代碼段,用來創建socket,綁定socekt,監聽socket
//初始化ngx_cycle_t結構
ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle)
{//創建監聽數組中的所有socketif (ngx_open_listening_sockets(cycle) != NGX_OK){goto failed;}//獲取發送緩沖區,接收緩沖區大小,以及監聽socketngx_configure_listening_sockets(cycle);
}
????????而函數ngx_open_listening_sockets將對監聽數組中存放的所有ip,port , 執行socket, 綁定socket, 監聽socket操作。如果失敗,則最多重復執行5次。
//創建監聽數組中的所有socket
ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle)
{//因此創建soceket,綁定socket, 監聽socket等操作有可能調用失敗。//失敗后最多嘗試5次for (tries = 5; tries; tries--)?{//將對所有監聽數組中的ip,port,開始創建socketls = cycle->listening.elts;for (i = 0; i < cycle->listening.nelts; i++){//創建套接字s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);//綁定套接字bind(s, ls[i].sockaddr, ls[i].socklen);//監聽套接字listen(s, ls[i].backlog);ls[i].listen = 1;ls[i].fd = s;}ngx_msleep(500);}
}
????????而ngx_configure_listening_sockets(ngx_cycle_t *cycle)函數只是簡單設置socket的一些選項,例如設置發送緩沖區,接收緩沖區大小,以及監聽隊列大小等。
????????到此監聽事件已經分析完了,至于把監聽socket加入到epoll中,則在ngx_trylock_accept_mutex函數中完成。將監聽socket加入到epoll在前面的文章中已經分析過了,在這里就不在分析了,可以參考master進程、work進程初始化這兩篇文章。
---------------------?
作者:ApeLife?
來源:CSDN?
原文:https://blog.csdn.net/apelife/article/details/53647316?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
總結
以上是生活随笔為你收集整理的nginx监听事件流程的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。