nginx socket初始化


1.  一些相關的數據結構:

//     監聽端口配置信息,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;
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;

監聽地址配置信息,包含了所有在該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的哈希表,用於快速查找對應虛擬主機的配置信息。

struct ngx_listening_s {
    ngx_socket_t        fd;

    struct sockaddr    *sockaddr;
    socklen_t           socklen;    /* size of sockaddr */
    size_t              addr_text_max_len;
    ngx_str_t           addr_text;

    int                 type;
    
    
    void               *servers;  /* array of ngx_http_in_addr_t, for example */

    ngx_log_t           log;
    ngx_log_t          *logp;
}

2.監聽socket的初始化

     nginx把需要監聽的socket用ngx_listening_t表示,存放在ngx_cycle中的listening數組中.具體的socket初始化的可以分為3個步驟:1.解析配置文件,獲取監聽socket的相關信息. 2.初始化監聽socket  3.打開並監聽socket.

     1)解析配置文件獲取socket相關信息

        用於設置監聽socket的指令只要有2個:server_name,listen. server_name用於實現虛擬主機的功能,會為每個server塊設置一個虛擬主機,在處理請求時會根據請求行中hosts進行轉發請求.而listen就是監聽socket的信息。這2個指令都在ngx_http_core_module中.首先看一下server_name指令的回調函數ngx_http_core_server_name,這個函數完成的功能很簡單就是將server_name指令指定的虛擬主機名添加到ngx_http_core_srv_conf_t的server_names數組中,以便在后面對整個web server支持的虛擬主機進行初始化

       a.  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;

    u_char                   ch;
    ngx_str_t               *value;
    ngx_uint_t               i;
    ngx_http_server_name_t  *sn;
    .........
    sn = ngx_array_push(&cscf->server_names);   //將指令sever_name的配置信息,添加到ngx_http_core_srv_conf_t中的sever_names數組中

    .....
      sn->server = cscf;

        if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) {
            sn->name = cf->cycle->hostname;

        } else {
            sn->name = value[i];
        }

        if (value[i].data[0] != '~') {
            ngx_strlow(sn->name.data, sn->name.data, sn->name.len);
            continue;
        }
}

b.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等。

    value = cf->args->elts;

    ngx_memzero(&u, sizeof(ngx_url_t));

    u.url = value[1];
    u.listen = 1;
    u.default_port = 80;
     //解析url中的ip地址,端口號,
    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;
    }
    ngx_http_listen_opt_t   lsopt;
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;

      (void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr,NGX_SOCKADDR_STRLEN, 1);//將2進至的地址信息,轉換文件

ngx_http_listen_opt_t  用於存儲listen socket配置信息.比如rcvbuf、sndbuf、backlog等,就是一些基本的socket選項.

    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屬性等。下面看一下具體實現

 

c. ngx_http_add_listen

ngx_int_t
ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    ngx_http_listen_opt_t *lsopt)
{
    port = ngx_array_push(cmcf->ports);  //將port放到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);
}

d .ngx_http_add_addresses

tatic ngx_int_t
ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
      return ngx_http_add_address(cf, cscf, port, lsopt);
}

f 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)
{
    ngx_http_conf_addr_t  *addr;

    if (port->addrs.elts == NULL) {
        if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
                           sizeof(ngx_http_conf_addr_t))
   
   

    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;
    addr->servers.elts = NULL;

    return ngx_http_add_server(cf, cscf, addr);
}
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) {
        if (ngx_array_init(&addr->servers, cf->temp_pool, 4,
                           sizeof(ngx_http_core_srv_conf_t *))
            != NGX_OK)
        {
            return NGX_ERROR;
        }

    } else {
        server = addr->servers.elts;
        for (i = 0; i < addr->servers.nelts; i++) {
            if (server[i] == cscf) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "a duplicate listen %s", addr->opt.addr);
                return NGX_ERROR;
            }
        }
    }

    server = ngx_array_push(&addr->servers);
    if (server == NULL) {
        return NGX_ERROR;
    }

    *server = cscf;

    return NGX_OK;
}

  最后,調用ngx_http_add_server將該server塊的server config添加到addr的servers數組中,這個函數邏輯很簡單,首先檢查是否存在重復的server config,如果存在則報錯,否則添加一個新的元素。

 2)初始化監聽socket

我們介紹了在函數ngx_http_block函數中調用ngx_http_optimize_servers函數完成ngx_listening_t初始化。

    1. ngx_http_optimize_server 

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;



    port = ports->elts;
    for (p = 0; p < ports->nelts; p++) {

        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++) {

            if (addr[a].servers.nelts > 1 )
            {
  1. 初始addr(ngx_http_conf_addr_t)中的hash、wc_head和wc_tail哈希表。 
  2.  這些哈希表以server name(虛擬主機名)為key,server塊的ngx_http_core_srv_conf_t為 
  3.  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;
}

     這個函數就是遍歷所有的端口號,將端口號對應的地址結構的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_create_listengint函數。

     4. ngx_create_listening

ngx_listening_t *
ngx_create_listening(ngx_conf_t *cf, void *sockaddr, socklen_t socklen)
{
    size_t            len;
    ngx_listening_t  *ls;
    struct sockaddr  *sa;
    u_char            text[NGX_SOCKADDR_STRLEN];

    ls = ngx_array_push(&cf->cycle->listening);  //將ngx_listening_t中的放到數組cycle->listening中

ls->sockaddr = sa;

      ls->socklen = socklen;

 
         

      len = ngx_sock_ntop(sa, text, NGX_SOCKADDR_STRLEN, 1);
      ls->addr_text.len = len;

}

3.打開socket監聽

   在ngx_init_cycle函數中,會調用ngx_open_listening_sockets和ngx_configure_listening_sockets函數完成監聽socket的打開和配置

  

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.

    通過這3個步驟就完成了監聽socket的初始化,接下來就會在ngx_event_process_init函數(ngx_event_core_moduel的process_init回調函數,在創建完worker進程后調用)中將這些監聽socket添加到事件循環中。

 

 

 

 

        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM