亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

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

本文以Nginx 1.5.1為例,從nginx_mail_smtp模塊如何進(jìn)行域名解析出發(fā),分析Nginx進(jìn)行域名解析的過(guò)程。為了簡(jiǎn)化流程,突出重點(diǎn),在示例代碼中省掉了一些異常部分的處理,比如內(nèi)存分配失敗等。

Nginx的DNS解析詳細(xì)過(guò)程分析(建議收藏)

 

DNS查詢分為兩種:根據(jù)域名查詢地址和根據(jù)地址查詢域名,在代碼結(jié)構(gòu)上這兩種方式非常相似,這里只介紹根據(jù)域名查詢地址這一種方式。本文將從以下幾個(gè)方面進(jìn)行介紹:

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

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

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

Nginx的DNS解析詳細(xì)過(guò)程分析(建議收藏)

 

為了不阻塞當(dāng)前線程,Nginx采用了異步的方式進(jìn)行域名查詢。整個(gè)查詢過(guò)程主要分為三個(gè)步驟,這點(diǎn)在各種異步處理時(shí)都是一樣的:

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

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

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

  1. 初始化域名查詢所需要的的全局信息
  2. 需要初始化的全局信息包括:
  3. 因?yàn)閞esolver是全局的,與任何一個(gè)connection都無(wú)關(guān),所有需要放在一個(gè)隨時(shí)都可以取到的地方,如 ngx_mail_core_srv_conf_t結(jié)構(gòu)體上,在使用時(shí)從當(dāng)前session找到ngx_mail_core_srv_conf_t,然后找到resolver。
  4. DNS 服務(wù)器的信息需要在配置文件中明確指出,比如
#nginx.conf
 
resolver 8.8.8.8
#nginx 默認(rèn)會(huì)根據(jù)DNS請(qǐng)求結(jié)果里的TTL值來(lái)進(jìn)行緩存,
#當(dāng)然也可以通過(guò)一個(gè)可選的參數(shù)valid來(lái)設(shè)置過(guò)期時(shí)間,如:
#resolver 127.0.0.1 [::1]:5353 valid=30s;
  1. 下面根據(jù)配置中的resolver參數(shù),初始化全局的ngx_resolver_t,其中保存了前面提及的DNS服務(wù)器地址和查詢結(jié)果等信息:
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;
}
  • DNS 服務(wù)器的地址,如果指定了多個(gè)服務(wù)器,nginx會(huì)采用Round Robin的方式輪流查詢每個(gè)服務(wù)器
  • 對(duì)查詢結(jié)果的緩存,采用Red Black Tree的數(shù)據(jù)結(jié)構(gòu),以要查詢名字的Hash作為Key, 節(jié)點(diǎn)信息存放在 struct ngx_resolver_node_t中。
  1. 準(zhǔn)備本次查詢的信息
  2. 和本次查詢相關(guān)的信息放在ngx_resolver_ctx_t結(jié)構(gòu)體中,包括要查詢的名稱,查詢完的回調(diào)方法,以及超時(shí)時(shí)間等。如果本次要查詢的地址已經(jīng)是IPv4用點(diǎn)分隔的地址了,比如74.125.128.100, nginx會(huì)在ngx_resolve_start中進(jìn)行判斷,并設(shè)置好標(biāo)志位,在調(diào)用ngx_resolve_name時(shí)不會(huì)發(fā)送真正的DNS查詢請(qǐng)求。
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ù)名字進(jìn)行IP地址查詢
 if (ngx_resolve_name(ctx) != NGX_OK) {
 ngx_mail_close_connection(c);
 }
}
  1. 根據(jù)名字進(jìn)行IP地址查詢
  2. 前面方法的最后通過(guò)ngx_resolve_name方法進(jìn)行IP地址查詢。查詢時(shí),Nginx會(huì)先檢查本地緩存,如果在緩存中,就更新緩存過(guò)期時(shí)間,并回調(diào)設(shè)置的handler, 如前面設(shè)置的:ngx_mail_smtp_resolve_name_handler,然后整個(gè)查詢過(guò)程結(jié)束。如果沒(méi)有在緩存中就發(fā)送查詢請(qǐng)求給dns server,同時(shí)方法返回。
  3. 查詢完成后回調(diào)在ngx_resolver_ctx_t中指定的方法
  4. 真正的DNS查詢完成后,不管成功,失敗或是超時(shí),nginx會(huì)回調(diào)相應(yīng)查詢的handler, 如前面設(shè)置的:ngx_mail_smtp_resolve_name_handler。在handler中都需要調(diào)用ngx_resolve_addr_done來(lái)標(biāo)識(shí)查詢結(jié)束。
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的DNS解析詳細(xì)過(guò)程分析(建議收藏)

 

通過(guò)Nginx進(jìn)行域名查詢的流程圖如下,顏色越深花費(fèi)的時(shí)間越長(zhǎng)。調(diào)用過(guò)程分為三種:

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

三、查詢場(chǎng)景分析及實(shí)現(xiàn)介紹

查詢的地址是IP v4地址

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

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;
 }
 }
 ...
}
  1. 超時(shí)沒(méi)有得到查詢結(jié)果
  2. 調(diào)用ngx_resolve_name時(shí)設(shè)置的回調(diào)方法被調(diào)用,同時(shí)ngx_resolver_ctx_t->state被設(shè)置為NGX_RESOLVE_TIMEDOUT。相應(yīng)的代碼為:
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);
}
  1. 正常查詢一個(gè)不在緩存中的域名
  2. 如果要查詢的域名不在緩存中,首先把域名按hash值放在緩存中,然后準(zhǔn)備查詢需要的數(shù)據(jù),發(fā)送DNS查詢的UDP請(qǐng)求給DNS服務(wù)器,
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)所有等待本域名解析的請(qǐng)求
 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);
 }
}
  1. 對(duì)同一域名查詢多次查詢
  2. 如果多次查詢時(shí),之前的查詢結(jié)果還在緩存中并且沒(méi)有失效,就直接從緩存中取到查詢結(jié)果,并調(diào)用設(shè)置的回調(diào)方法。
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;
 }
 }
 }
}
  1. 得到查詢結(jié)果時(shí)同時(shí)超時(shí)了
  2. 如果在得到查詢結(jié)果的同時(shí),設(shè)置的超時(shí)時(shí)間也到期了,那該怎么辦呢?Nginx會(huì)先處理各種網(wǎng)絡(luò)讀寫(xiě)事件,再處理超時(shí)事件,在處理網(wǎng)絡(luò)事件時(shí),會(huì)相應(yīng)地把設(shè)置的定時(shí)器刪除,所以在執(zhí)行超時(shí)事件時(shí)就不會(huì)再執(zhí)行了。
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事件,其中包含了查詢超時(shí)
 ngx_event_expire_timers();
}
  1. 得到查詢結(jié)果時(shí)客戶端已經(jīng)關(guān)閉連接
  2. 如果不做任何處理,那么在收到dns查詢結(jié)果后,會(huì)回調(diào)查詢時(shí)設(shè)置的回調(diào)方法,但因?yàn)檫B接已經(jīng)被關(guān)閉,相應(yīng)的內(nèi)存已經(jīng)被釋放,所以會(huì)有非法內(nèi)存訪問(wèn)的問(wèn)題。怎么避免呢?在處理連接關(guān)閉事件時(shí),同時(shí)需要調(diào)用ngx_resolve_name_done(ctx)方法,調(diào)用時(shí)需要把state設(shè)為NGX_AGAIN或者NGX_RESOLVE_TIMEDOUT,這樣就會(huì)刪除查詢所設(shè)置的回調(diào)信息:
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);
}
  1. 本地緩存的地址沒(méi)有再次被查詢
  2. 每次在查詢結(jié)束的時(shí)候(調(diào)用ngx_resolve_addr_done),都會(huì)檢查有沒(méi)有緩存過(guò)期,如果有,就會(huì)進(jìn)行釋放。
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);
 }
}
  1. 域名對(duì)應(yīng)這多個(gè)IP地址
  2. 如果對(duì)應(yīng)的有多個(gè)ip,那么在每次查詢時(shí),會(huì)隨機(jī)的重新排列順序,然后返回。對(duì)于調(diào)用者來(lái)說(shuō),只要去第一個(gè)地址,就可以達(dá)到取隨機(jī)地址的目的了。
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;
}
  1. 指定了多個(gè)dns server地址會(huì)怎么查詢
  2. 如果在配置文件里指定了多個(gè)dns server地址會(huì)發(fā)生什么呢?比如
#nginx.conf
resolver 8.8.8.8 8.8.4.4
  1. 那么nginx 會(huì)采用Round Robin 的方式輪流查詢各個(gè)dns server。在方法ngx_resolver_send_query中通過(guò)在每次調(diào)用時(shí)改變last_connection來(lái)輪流使用不同的dns server進(jìn)行查詢
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;
 }
 ...

end:如果你覺(jué)得本文對(duì)你有幫助的話,記得關(guān)注點(diǎn)贊轉(zhuǎn)發(fā),你的支持就是我更新動(dòng)力。

分享到:
標(biāo)簽:Nginx
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定