久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Nginx的DNS解析過程分析

 lycbje 2014-01-02

Nginx怎么做域名解析,?怎么在你自己開發(fā)的模塊里面使用Nginx提供的方法解析域名,?它內(nèi)部實現(xiàn)是什么樣的?

本文以Nginx 1.5.1為例,,從nginx_mail_smtp模塊如何進行域名解析出發(fā),,分析Nginx進行域名解析的過程。為了簡化流程,,突出重點,,在示例代碼中省掉了一些異常部分的處理,比如內(nèi)存分配失敗等,。DNS查詢分為兩種:根據(jù)域名查詢地址和根據(jù)地址查詢域名,,在代碼結(jié)構(gòu)上這兩種方式非常相似,這里只介紹根據(jù)域名查詢地址這一種方式,。本文將從以下幾個方面進行介紹:

  1. 域名查詢的函數(shù)接口介紹
  2. 域名解析流程分析
  3. 查詢場景分析及實現(xiàn)介紹

一,、域名查詢的函數(shù)接口介紹

在使用同步IO的情況下,調(diào)用gethostbyname()或者gethostbyname_r()就可以根據(jù)域名查詢到對應(yīng)的IP地址, 但因為可能會通過網(wǎng)絡(luò)進行遠程查詢,,所以需要的時間比較長,。

為了不阻塞當前線程,Nginx采用了異步的方式進行域名查詢,。整個查詢過程主要分為三個步驟,,這點在各種異步處理時都是一樣的:

  1. 準備函數(shù)調(diào)用需要的信息,并設(shè)置回調(diào)方法
  2. 調(diào)用函數(shù)
  3. 處理結(jié)束后回調(diào)方法被調(diào)用

另外,,為了盡量減少查詢花費的時間,,Nginx還對查詢結(jié)果做了本地緩存。為了初始化DNS Server地址和本地緩存等信息,,需要在真正查詢前需要先進行一些全局的初始化操作,。

下面先從調(diào)用者的角度對每個步驟做詳細的分析:

  1. 初始化域名查詢所需要的的全局信息

    需要初始化的全局信息包括:

    • DNS 服務(wù)器的地址,如果指定了多個服務(wù)器,,nginx會采用Round Robin的方式輪流查詢每個服務(wù)器
    • 對查詢結(jié)果的緩存,,采用Red Black Tree的數(shù)據(jù)結(jié)構(gòu),以要查詢名字的Hash作為Key, 節(jié)點信息存放在 struct ngx_resolver_node_t中,。

    因為resolver是全局的,,與任何一個connection都無關(guān),所有需要放在一個隨時都可以取到的地方,,如 ngx_mail_core_srv_conf_t結(jié)構(gòu)體上,,在使用時從當前session找到ngx_mail_core_srv_conf_t,然后找到resolver,。

    DNS 服務(wù)器的信息需要在配置文件中明確指出,,比如

    1
    2
    3
    4
    5
    6
    #nginx.conf
    resolver 8.8.8.8
    #nginx 默認會根據(jù)DNS請求結(jié)果里的TTL值來進行緩存,
    #當然也可以通過一個可選的參數(shù)valid來設(shè)置過期時間,如:
    #resolver 127.0.0.1 [::1]:5353 valid=30s;

    下面根據(jù)配置中的resolver參數(shù),,初始化全局的ngx_resolver_t,,其中保存了前面提及的DNS服務(wù)器地址和查詢結(jié)果等信息:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    static char *
    ngx_mail_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
        ngx_mail_core_srv_conf_t  *cscf = conf;
        ngx_str_t  *value;
        value = cf->args->elts;
        cscf->resolver = ngx_resolver_create(cf, &value[1],
                                             cf->args->nelts - 1);
        return NGX_CONF_OK;
    }
  2. 準備本次查詢的信息

    和本次查詢相關(guān)的信息放在ngx_resolver_ctx_t結(jié)構(gòu)體中,包括要查詢的名稱,,查詢完的回調(diào)方法,以及超時時間等,。如果本次要查詢的地址已經(jīng)是IPv4用點分隔的地址了,,比如74.125.128.100, nginx會在ngx_resolve_start中進行判斷,并設(shè)置好標志位,,在調(diào)用ngx_resolve_name時不會發(fā)送真正的DNS查詢請求,。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    static void
    ngx_mail_smtp_resolve_name(ngx_event_t *rev)
    {
        ngx_connection_t          *c;
        ngx_mail_session_t        *s;
        ngx_resolver_ctx_t        *ctx;
        ngx_mail_core_srv_conf_t  *cscf;
        c = rev->data;
        s = c->data;
        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
        ctx = ngx_resolve_start(cscf->resolver, NULL);
        if (ctx == NULL) {
            ngx_mail_close_connection(c);
            return;
        }
        ctx->name = s->host;
        ctx->type = NGX_RESOLVE_A;
        ctx->handler = ngx_mail_smtp_resolve_name_handler;
        ctx->data = s;
        ctx->timeout = cscf->resolver_timeout;
        //根據(jù)名字進行IP地址查詢
        if (ngx_resolve_name(ctx) != NGX_OK) {
            ngx_mail_close_connection(c);
        }
    }
  3. 根據(jù)名字進行IP地址查詢

    前面方法的最后通過ngx_resolve_name方法進行IP地址查詢。查詢時,,Nginx會先檢查本地緩存,,如果在緩存中,就更新緩存過期時間,,并回調(diào)設(shè)置的handler, 如前面設(shè)置的:ngx_mail_smtp_resolve_name_handler,,然后整個查詢過程結(jié)束。如果沒有在緩存中就發(fā)送查詢請求給dns server,,同時方法返回,。

  4. 查詢完成后回調(diào)在ngx_resolver_ctx_t中指定的方法

    真正的DNS查詢完成后,不管成功,,失敗或是超時,,nginx會回調(diào)相應(yīng)查詢的handler, 如前面設(shè)置的:ngx_mail_smtp_resolve_name_handler。在handler中都需要調(diào)用ngx_resolve_addr_done來標識查詢結(jié)束,。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    static void
    ngx_mail_smtp_resolve_name_handler(ngx_resolver_ctx_t *ctx)
    {
        in_addr_t            addr;
        ngx_uint_t           i;
        ngx_connection_t    *c;
        struct sockaddr_in  *sin;
        ngx_mail_session_t  *s;
        s = ctx->data;
        c = s->connection;
        if (ctx->state) {
            ngx_log_error(NGX_LOG_ERR, c->log, 0,
                          ""%V" could not be resolved (%i: %s)",
                          &ctx->name, ctx->state,
                          ngx_resolver_strerror(ctx->state));
        } else {
            /* AF_INET only */
            sin = (struct sockaddr_in *) c->sockaddr;
            for (i = 0; i < ctx->naddrs; i++) {
                addr = ctx->addrs[i];
                ngx_log_debug4(NGX_LOG_DEBUG_MAIL, c->log, 0,
                               "name was resolved to %ud.%ud.%ud.%ud",
                               (ntohl(addr) >> 24) & 0xff,
                               (ntohl(addr) >> 16) & 0xff,
                               (ntohl(addr) >> 8) & 0xff,
                               ntohl(addr) & 0xff);
                if (addr == sin->sin_addr.s_addr) {
                    goto found;
                }
            }
            s->host = smtp_unavailable;
        }
    found:
        //不管成功失敗都要執(zhí)行
        ngx_resolve_name_done(ctx);
    }

二,、域名解析流程分析

通過Nginx進行域名查詢的流程圖如下,顏色越深花費的時間越長,。調(diào)用過程分為三種:

  1. 首先判斷是不是IPv4地址,,如果是就直接調(diào)用Handler
  2. 再次檢查是不是在緩存中,如果有,,就調(diào)用Handler
  3. 最后發(fā)送遠程DNS請求,,收到回復(fù)后調(diào)用Handler

nginx_dns_resolve (3)

三、查詢場景分析及實現(xiàn)介紹

  1. 查詢的地址是IP v4地址

    比如74.125.128.100, nginx會在ngx_resolve_start中通過ngx_inet_addr方法進行判斷,,如果是IPv4的地址,,就設(shè)置好標志位 ngx_resolver_ctx_t->quick,在接下來的ngx_resolve_name中會對這個標志位進行判斷,,如果為1,,就直接調(diào)用ngx_resolver_ctx_t->handler

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    ngx_resolver_ctx_t *
    ngx_resolve_start(ngx_resolver_t *r, ngx_resolver_ctx_t *temp)
    {
        in_addr_t            addr;
        ngx_resolver_ctx_t  *ctx;
        if (temp) {
            addr = ngx_inet_addr(temp->name.data, temp->name.len);
            if (addr != INADDR_NONE) {
                temp->resolver = r;
                temp->state = NGX_OK;
                temp->naddrs = 1;
                temp->addrs = &temp->addr;
                temp->addr = addr;
                temp->quick = 1;
                return temp;
            }
        }
        ...
    }
  2. 超時沒有得到查詢結(jié)果

    調(diào)用ngx_resolve_name時設(shè)置的回調(diào)方法被調(diào)用,同時ngx_resolver_ctx_t->state被設(shè)置為NGX_RESOLVE_TIMEDOUT。相應(yīng)的代碼為:

    1
    2
    3
    4
    5
    6
    7
    8
    static void
    ngx_resolver_timeout_handler(ngx_event_t *ev)
    {
        ngx_resolver_ctx_t  *ctx;
        ctx = ev->data;
        ctx->state = NGX_RESOLVE_TIMEDOUT;
        ctx->handler(ctx);
    }
  3. 正常查詢一個不在緩存中的域名

    如果要查詢的域名不在緩存中,,首先把域名按hash值放在緩存中,,然后準備查詢需要的數(shù)據(jù),發(fā)送DNS查詢的UDP請求給DNS服務(wù)器,,

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    static ngx_int_t
    ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx)
    {
        ngx_resolver_node_t  *rn;
        rn = ngx_resolver_alloc(r, sizeof(ngx_resolver_node_t));
        ngx_rbtree_insert(&r->name_rbtree, &rn->node);
        ngx_resolver_create_name_query(rn, ctx);
        ngx_resolver_send_query(r, rn);
        rn->cnlen = 0;
        rn->naddrs = 0;
        rn->valid = 0;
        rn->waiting = ctx;
        ctx->state = NGX_AGAIN;
    }
    //收到DNS查詢結(jié)果后的回調(diào)方法
    static void
    ngx_resolver_read_response(ngx_event_t *rev)
    {
        ssize_t            n;
        ngx_connection_t  *c;
        u_char             buf[NGX_RESOLVER_UDP_SIZE];
        c = rev->data;
        do {
            n = ngx_udp_recv(c, buf, NGX_RESOLVER_UDP_SIZE);
            if (n < 0) {
                return;
            }
            ngx_resolver_process_response(c->data, buf, n);
        } while (rev->ready);
    }
    static void
    ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t last,
        ngx_uint_t ident, ngx_uint_t code, ngx_uint_t nan, ngx_uint_t ans)
    {
        hash = ngx_crc32_short(name.data, name.len);
        rn = ngx_resolver_lookup_name(r, &name, hash);
        //copy addresses to cached node
        rn->u.addrs = addrs;
        //回調(diào)所有等待本域名解析的請求
        next = rn->waiting;
        rn->waiting = NULL;
        while (next) {
             ctx = next;
             ctx->state = NGX_OK;
             ctx->naddrs = naddrs;
             ctx->addrs = (naddrs == 1) ? &ctx->addr : addrs;
             ctx->addr = addr;
             next = ctx->next;
             ctx->handler(ctx);
        }
    }
  4. 對同一域名查詢多次查詢

    如果多次查詢時,,之前的查詢結(jié)果還在緩存中并且沒有失效,就直接從緩存中取到查詢結(jié)果,,并調(diào)用設(shè)置的回調(diào)方法,。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    static ngx_int_t
    ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx)
    {
        uint32_t              hash;
        in_addr_t             addr, *addrs;
        ngx_uint_t            naddrs;
        ngx_resolver_ctx_t   *next;
        ngx_resolver_node_t  *rn;
        hash = ngx_crc32_short(ctx->name.data, ctx->name.len);
        rn = ngx_resolver_lookup_name(r, &ctx->name, hash);
        if (rn) {
            if (rn->valid >= ngx_time()) {
                naddrs = rn->naddrs;
                if (naddrs) {
                    ctx->next = rn->waiting;
                    rn->waiting = NULL;
                    do {
                        ctx->state = NGX_OK;
                        ctx->naddrs = naddrs;
                        ctx->addrs = (naddrs == 1) ? &ctx->addr : addrs;
                        ctx->addr = addr;
                        next = ctx->next;
                        ctx->handler(ctx);
                        ctx = next;
                    } while (ctx);
                    return NGX_OK;
                }
            }
        }
    }
  5. 得到查詢結(jié)果時同時超時了

    如果在得到查詢結(jié)果的同時,設(shè)置的超時時間也到期了,,那該怎么辦呢,? Nginx會先處理各種網(wǎng)絡(luò)讀寫事件,再處理超時事件,,在處理網(wǎng)絡(luò)事件時,,會相應(yīng)地把設(shè)置的定時器刪除,所以在執(zhí)行超時事件時就不會再執(zhí)行了,。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    void
    ngx_process_events_and_timers(ngx_cycle_t *cycle)
    {
        ngx_uint_t  flags;
        ngx_msec_t  timer, delta;
        //處理各種網(wǎng)絡(luò)事件
        (void) ngx_process_events(cycle, timer, flags);
        //處理各種timer事件,,其中包含了查詢超時
        ngx_event_expire_timers();
    }
  6. 得到查詢結(jié)果時客戶端已經(jīng)關(guān)閉連接

    如果不做任何處理,那么在收到dns查詢結(jié)果后,,會回調(diào)查詢時設(shè)置的回調(diào)方法,,但因為連接已經(jīng)被關(guān)閉,相應(yīng)的內(nèi)存已經(jīng)被釋放,,所以會有非法內(nèi)存訪問的問題,。怎么避免呢?在處理連接關(guān)閉事件時,,同時需要調(diào)用ngx_resolve_name_done(ctx)方法,調(diào)用時需要把state設(shè)為NGX_AGAIN或者NGX_RESOLVE_TIMEDOUT,,這樣就會刪除查詢所設(shè)置的回調(diào)信息:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    void ngx_close_xxx_session(ngx_xxx_session_t *s)
    {
        if(s->resolver_ctx != NULL) {
            s->resolver_ctx->state = NGX_RESOLVE_TIMEDOUT;
            ngx_resolve_name_done(s->resolver_ctx);
            s->resolver_ctx = NULL;
        }
    }
    void ngx_resolve_name_done(ngx_resolver_ctx_t *ctx)
    {
        uint32_t              hash;
        ngx_resolver_t       *r;
        ngx_resolver_ctx_t   *w, **p;
        ngx_resolver_node_t  *rn;
        r = ctx->resolver;
        if (ctx->state == NGX_AGAIN || ctx->state == NGX_RESOLVE_TIMEDOUT) {
            hash = ngx_crc32_short(ctx->name.data, ctx->name.len);
            rn = ngx_resolver_lookup_name(r, &ctx->name, hash);
            if (rn) {
                p = &rn->waiting;
                w = rn->waiting;
                while (w) {
                    if (w == ctx) {
                        *p = w->next;
                        goto done;
                    }
                    p = &w->next;
                    w = w->next;
                }
            }
        }
    done:
        ngx_resolver_free_locked(r, ctx);
    }
  7. 本地緩存的地址沒有再次被查詢

    每次在查詢結(jié)束的時候(調(diào)用ngx_resolve_addr_done),都會檢查有沒有緩存過期,,如果有,,就會進行釋放。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    static void
    ngx_resolver_expire(ngx_resolver_t *r, ngx_rbtree_t *tree,
                        ngx_queue_t *queue)
    {
        time_t                now;
        ngx_uint_t            i;
        ngx_queue_t          *q;
        ngx_resolver_node_t  *rn;
        now = ngx_time();
        for (i = 0; i < 2; i++) {
            if (ngx_queue_empty(queue)) {
                return;
            }
            q = ngx_queue_last(queue);
            rn = ngx_queue_data(q, ngx_resolver_node_t, queue);
            if (now <= rn->expire) {
                return;
            }
            ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0,
                "resolver expire "%*s"", (size_t) rn->nlen, rn->name);
            ngx_queue_remove(q);
            ngx_rbtree_delete(tree, &rn->node);
            ngx_resolver_free_node(r, rn);
        }
    }
  8. 域名對應(yīng)這多個IP地址

    如果對應(yīng)的有多個ip,那么在每次查詢時,,會隨機的重新排列順序,,然后返回。對于調(diào)用者來說,,只要去第一個地址,,就可以達到取隨機地址的目的了。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    static ngx_int_t
    ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx)
    {
        if (naddrs) {
            if (naddrs != 1) {
                addr = 0;
                addrs = ngx_resolver_rotate(r, rn->u.addrs, naddrs);
                if (addrs == NULL) {
                    return NGX_ERROR;
                }
            } else {
                addr = rn->u.addr;
                addrs = NULL;
            }
        }
    }
    static in_addr_t *
    ngx_resolver_rotate(ngx_resolver_t *r, in_addr_t *src, ngx_uint_t n)
    {
        void        *dst, *p;
        ngx_uint_t   j;
        dst = ngx_resolver_alloc(r, n * sizeof(in_addr_t));
        j = ngx_random() % n;
        if (j == 0) {
            ngx_memcpy(dst, src, n * sizeof(in_addr_t));
            return dst;
        }
        p = ngx_cpymem(dst, &src[j], (n - j) * sizeof(in_addr_t));
        ngx_memcpy(p, src, j * sizeof(in_addr_t));
        return dst;
    }
  9. 指定了多個dns server地址會怎么查詢

    如果在配置文件里指定了多個dns server地址會發(fā)生什么呢,?比如

    1
    2
    #nginx.conf
    resolver 8.8.8.8 8.8.4.4

    那么nginx 會采用Round Robin 的方式輪流查詢各個dns server,。在方法ngx_resolver_send_query中通過在每次調(diào)用時改變last_connection來輪流使用不同的dns server進行查詢

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    static ngx_int_t
    ngx_resolver_send_query(ngx_resolver_t *r, ngx_resolver_node_t *rn)
    {
        ssize_t                n;
        ngx_udp_connection_t  *uc;
        uc = r->udp_connections.elts;
        uc = &uc[r->last_connection++];
        if (r->last_connection == r->udp_connections.nelts) {
            r->last_connection = 0;
        }
        ...
    }

一邊用一邊學一邊寫,會理解的更透徹,祝大家也玩的高興,。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多