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;
#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
|---> 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;
#endif
default: /* 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函式新增地址資訊。
port = ngx_array_push(cmcf->ports);
if (port == NULL) {
return NGX_ERROR;
}
port->family = sa->sa_family;
port->port = p;
port->addrs.elts = NULL;
return ngx_http_add_address(cf, cscf, port, lsopt);
下面先看一下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;
#endif
default: /* AF_INET */
off = offsetof(struct sockaddr_in, sin_addr);
len = 4;
break;
}
這段程式碼是確定具體協議型別的地址資訊在地址結構中的偏移量和長度,因為不能比較整個地址結構。
p = lsopt->u.sockaddr_data + off;
addr = port->addrs.elts;
/**
* 在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_conf_addr_t的servers陣列新增ngx_http_core_srv_conf_t結構
*/
if (ngx_http_add_server(cf, cscf, &addr[i]) != NGX_OK) {
return NGX_ERROR;
}
/* 設定default_server屬性 */
/* preserve default_server bit during listen options overwriting */
default_server = addr[i].opt.default_server;
#if (NGX_HTTP_SSL)
ssl = lsopt->ssl || addr[i].opt.ssl;
#endif
if (lsopt->set) {
if (addr[i].opt.set) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"a duplicate listen options for %s", addr[i].opt.addr);
return NGX_ERROR;
}
addr[i].opt = *lsopt;
}
/* check the duplicate "default" server for this address:port */
if (lsopt->default_server) {
if (default_server) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"a duplicate default server for %s", addr[i].opt.addr);
return NGX_ERROR;
}
default_server = 1;
addr[i].default_server = cscf;
}
addr[i].opt.default_server = default_server;
#if (NGX_HTTP_SSL)
addr[i].opt.ssl = ssl;
#endif
return NGX_OK;
}
如果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;
#endif
addr->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。
return ngx_http_add_server(cf, cscf, addr);
最後,呼叫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;
#endif
default: /* 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
ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);
if (ls == NULL) {
return NULL;
}
呼叫ngx_create_listening函式建立ngx_listening_t,這個函式的內容在下面分析。
// 設定監聽socket的handler,在監聽到新的連線時呼叫,即ngx_event_accept中。
ls->handler = ngx_http_init_connection;
設定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
ngx_int_t
ngx_open_listening_sockets(ngx_cycle_t *cycle)
{
int reuseaddr;
ngx_uint_t i, tries, failed;
ngx_err_t err;
ngx_log_t *log;
ngx_socket_t s;
ngx_listening_t *ls;
reuseaddr = 1;
#if (NGX_SUPPRESS_WARN)
failed = 0;
#endif
log = cycle->log;
/* TODO: configurable try number */
for (tries = 5; tries; tries--) {
failed = 0;
/* for each listening socket */
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
if (ls[i].ignore) {
continue;
}
/**
* 已經從就cycle處繼承該socket,不需重新開啟
*/
if (ls[i].fd != -1) {
continue;
}
if (ls[i].inherited) {
/* TODO: close on exit */
/* TODO: nonblocking */
/* TODO: deferred accept */
continue;
}
/* 新建一個socket,socket(地址結構的協議族,socket型別tcp/udp,)*/
s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);
if (s == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_socket_n " %V failed", &ls[i].addr_text);
return NGX_ERROR;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
(const void *) &reuseaddr, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"setsockopt(SO_REUSEADDR) %V failed",
&ls[i].addr_text);
if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}
return NGX_ERROR;
}
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
if (ls[i].sockaddr->sa_family == AF_INET6 && ls[i].ipv6only) {
int ipv6only;
ipv6only = (ls[i].ipv6only == 1);
if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
(const void *) &ipv6only, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"setsockopt(IPV6_V6ONLY) %V failed, ignored",
&ls[i].addr_text);
}
}
#endif
/* TODO: close on exit */
if (!(ngx_event_flags & NGX_USE_AIO_EVENT)) {
/* 將socket設定為非阻塞 */
if (ngx_nonblocking(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_nonblocking_n " %V failed",
&ls[i].addr_text);
if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}
return NGX_ERROR;
}
}
ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0,
"bind() %V #%d ", &ls[i].addr_text, s);
/* 將socket繫結到要監聽的地址 */
if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
err = ngx_socket_errno;
if (err == NGX_EADDRINUSE && ngx_test_config) {
continue;
}
ngx_log_error(NGX_LOG_EMERG, log, err,
"bind() to %V failed", &ls[i].addr_text);
if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}
if (err != NGX_EADDRINUSE) {
return NGX_ERROR;
}
failed = 1;
continue;
}
#if (NGX_HAVE_UNIX_DOMAIN)
/* 處理unix domain socket */
if (ls[i].sockaddr->sa_family == AF_UNIX) {
mode_t mode;
u_char *name;
name = ls[i].addr_text.data + sizeof("unix:") - 1;
mode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if (chmod((char *) name, mode) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"chmod() \"%s\" failed", name);
}
if (ngx_test_config) {
if (ngx_delete_file(name) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
ngx_delete_file_n " %s failed", name);
}
}
}
#endif
/* 將socket轉換為監聽socket,backlog指定了核心為改監聽socket排隊的最大值 */
if (listen(s, ls[i].backlog) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"listen() to %V, backlog %d failed",
&ls[i].addr_text, ls[i].backlog);
if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}
return NGX_ERROR;
}
/* 標識是監聽socket */
ls[i].listen = 1;
/* 設定ngx_listening_t的描述符 */
ls[i].fd = s;
}
if (!failed) {
break;
}
/* TODO: delay configurable */
ngx_log_error(NGX_LOG_NOTICE, log, 0,
"try again to bind() after 500ms");
ngx_msleep(500);
}
if (failed) {
ngx_log_error(NGX_LOG_EMERG, log, 0, "still could not bind()");
return NGX_ERROR;
}
return NGX_OK;
}
這個函式就是開啟socket,和一般socket程式設計一樣,就是呼叫socket、bind和listen,具體看一下注釋。
2. ngx_configure_listening_sockets
void
ngx_configure_listening_sockets(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_listening_t *ls;
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
struct accept_filter_arg af;
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
int timeout;
#endif
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
ls[i].log = *ls[i].logp;
/**
* 設定監聽socket的接收緩衝區大小
*/
if (ls[i].rcvbuf != -1) {
if (setsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF,
(const void *) &ls[i].rcvbuf, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_RCVBUF, %d) %V failed, ignored",
ls[i].rcvbuf, &ls[i].addr_text);
}
}
/**
* 設定監聽socket的傳送緩衝區大小
*/
if (ls[i].sndbuf != -1) {
if (setsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF,
(const void *) &ls[i].sndbuf, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_SNDBUF, %d) %V failed, ignored",
ls[i].sndbuf, &ls[i].addr_text);
}
}
#if (NGX_HAVE_SETFIB)
if (ls[i].setfib != -1) {
if (setsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB,
(const void *) &ls[i].setfib, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(SO_SETFIB, %d) %V failed, ignored",
ls[i].setfib, &ls[i].addr_text);
}
}
#endif
#if 0
if (1) {
int tcp_nodelay = 1;
if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_NODELAY,
(const void *) &tcp_nodelay, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"setsockopt(TCP_NODELAY) %V failed, ignored",
&ls[i].addr_text);
}
}
#endif
if (ls[i].listen) {
/* change backlog via listen() */
if (listen(ls[i].fd, ls[i].backlog) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
"listen() to %V, backlog %d failed, ignored",
&ls[i].addr_text, ls[i].backlog);
}
}
/*
* setting deferred mode should be last operation on socket,
* because code may prematurely continue cycle on failure
*/
#if (NGX_HAVE_DEFERRED_ACCEPT)
#ifdef SO_ACCEPTFILTER
if (ls[i].delete_deferred) {
/**
* set accept filter on listening socket
* 將SO_ACCEPTFILTER設定為NULL,也就是刪除accept filter
*/
if (setsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0)
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setsockopt(SO_ACCEPTFILTER, NULL) "
"for %V failed, ignored",
&ls[i].addr_text);
if (ls[i].accept_filter) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"could not change the accept filter "
"to \"%s\" for %V, ignored",
ls[i].accept_filter, &ls[i].addr_text);
}
continue;
}
ls[i].deferred_accept = 0;
}
if (ls[i].add_deferred) {
ngx_memzero(&af, sizeof(struct accept_filter_arg));
(void) ngx_cpystrn((u_char *) af.af_name,
(u_char *) ls[i].accept_filter, 16);
/**
* set accept filter on listening socket
* 將SO_ACCEPTFILTER設定為ls[i].accept_filter,也就是新增accept filter
*/
if (setsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER,
&af, sizeof(struct accept_filter_arg))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setsockopt(SO_ACCEPTFILTER, \"%s\") "
" for %V failed, ignored",
ls[i].accept_filter, &ls[i].addr_text);
continue;
}
ls[i].deferred_accept = 1;
}
#endif
#ifdef TCP_DEFER_ACCEPT
if (ls[i].add_deferred || ls[i].delete_deferred) {
if (ls[i].add_deferred) {
timeout = (int) (ls[i].post_accept_timeout / 1000);
} else {
timeout = 0;
}
if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_DEFER_ACCEPT,
&timeout, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setsockopt(TCP_DEFER_ACCEPT, %d) for %V failed, "
"ignored",
timeout, &ls[i].addr_text);
continue;
}
}
if (ls[i].add_deferred) {
ls[i].deferred_accept = 1;
}
#endif
#endif /* NGX_HAVE_DEFERRED_ACCEPT */
}
return;
}
這個函式就是根據配置呼叫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
版權宣告:本文為博主原創文章,轉載請附上博文連結!