上一篇文章分析了nginx是如何管理監(jiān)聽(tīng)事件,并把監(jiān)聽(tīng)事件注冊(cè)到epoll事件管理器中。接下來(lái)在這基礎(chǔ)上分析當(dāng)有客戶端連接請(qǐng)求到來(lái)時(shí),nginx是如何與客戶端建立tcp連接,以及連接建立后又是如何管理超時(shí)事件。
一、連接事件管理
在函數(shù)ngx_event_process_init中,會(huì)設(shè)置讀事件的回調(diào)為ngx_event_accept。 這樣設(shè)置后,在nginx服務(wù)器監(jiān)聽(tīng)到來(lái)自客戶端的連接請(qǐng)求后,該回調(diào)會(huì)被觸發(fā),用來(lái)與客戶端建立tcp連接。連接建立后,就可以正常與客戶端進(jìn)行數(shù)據(jù)交互。
- //ngx_event_core_module模塊的init_process方法。在函數(shù)ngx_worker_process_init中被調(diào)用
- static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
- {
- //對(duì)于每一個(gè)監(jiān)聽(tīng)端口,從連接池中取出一個(gè)連接對(duì)象(也將從讀時(shí)間,寫事件池取出對(duì)象,
- //使得連接,讀、寫保持一一對(duì)應(yīng)關(guān)系),負(fù)責(zé)監(jiān)聽(tīng)來(lái)自客戶端的連接
- ls = cycle->listening.elts;
- for (i = 0; i < cycle->listening.nelts; i++)
- {
- c = ngx_get_connection(ls[i].fd, cycle->log);
- //建立連接對(duì)象與監(jiān)聽(tīng)對(duì)象的關(guān)系
- c->listening = &ls[i];
- //建立監(jiān)聽(tīng)對(duì)象與連接對(duì)象的關(guān)系
- ls[i].connection = c;
- rev = c->read;
-
- //設(shè)置連接回調(diào),當(dāng)有客戶端連接時(shí),將觸發(fā)回調(diào)
- rev->handler = ngx_event_accept;
-
- //如果work進(jìn)程之間沒(méi)有使用枷鎖,則把讀事件加入epoll中
- //此時(shí)寫事件的回調(diào)為NULL,因?yàn)樵趎gx_get_connection函數(shù)中會(huì)把整個(gè)結(jié)構(gòu)進(jìn)行清0操作
- if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR)
- {
- return NGX_ERROR;
- }
- }
ngx_event_accept用來(lái)接收來(lái)自客戶端的連接請(qǐng)求。tcp建立后,從連接池中獲取一個(gè)新連接對(duì)象(同時(shí)獲取到讀、寫事件), 并把讀事件加入到epoll中。這個(gè)新連接對(duì)象與監(jiān)聽(tīng)對(duì)象作用是不同的。 監(jiān)聽(tīng)對(duì)象注意用來(lái)監(jiān)聽(tīng)來(lái)自客戶端的連接,是還沒(méi)有與客戶端建立連接前被調(diào)用。而這個(gè)新連接對(duì)象是在tcp連接后被調(diào)用,用來(lái)與客戶端進(jìn)行數(shù)據(jù)讀寫。
- //客戶端請(qǐng)求連接回調(diào)
- void ngx_event_accept(ngx_event_t *ev)
- {
- do
- {
- //接收客戶端連接
- s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
-
- //work進(jìn)程之間賦值均衡,但一個(gè)work進(jìn)程超過(guò)每一個(gè)進(jìn)程的最大連接數(shù)的7/8時(shí),
- //則該work進(jìn)程不在監(jiān)聽(tīng)來(lái)自客戶端的連接請(qǐng)求。但已經(jīng)建立tcp連接的客戶端不收影響,正常進(jìn)行數(shù)據(jù)讀寫
- ngx_accept_disabled = ngx_cycle->connection_n / 8
- - ngx_cycle->free_connection_n;
-
- //獲取一個(gè)空閑連接對(duì)象(同時(shí)也獲取到讀,寫事件)
- c = ngx_get_connection(s, ev->log);
-
- //給新連接對(duì)象賦值
- c->pool = ngx_create_pool(ls->pool_size, ev->log);
- c->sockaddr = ngx_palloc(c->pool, socklen);
- ngx_memcpy(c->sockaddr, sa, socklen);
-
- //設(shè)置從內(nèi)核讀取數(shù)據(jù),寫入數(shù)據(jù)的的公共方法。這些方法實(shí)際上就是ngx_os_io結(jié)構(gòu)的各個(gè)成員
- //這些方法為什么不設(shè)置在事件對(duì)象上,而是設(shè)置在連接對(duì)象。因?yàn)檫@對(duì)讀寫事件而言,這些方法是公共的。
- //連接對(duì)象里面包含了讀寫事件對(duì)象的引用關(guān)系,如果設(shè)置在相應(yīng)的讀事件,或者寫事件上,則每個(gè)事件都需要設(shè)置一次
- //而在連接對(duì)象上只需要設(shè)置一次
- c->recv = ngx_recv;
- c->send = ngx_send;
- c->recv_chain = ngx_recv_chain;
- c->send_chain = ngx_send_chain;
-
- //連接對(duì)象里面的監(jiān)聽(tīng)指針指向ls,但ls并沒(méi)有把連接指向當(dāng)前已經(jīng)調(diào)用accept的這個(gè)連接,
- //而是指向監(jiān)聽(tīng)連接對(duì)象
- c->listening = ls;
-
- //調(diào)用監(jiān)聽(tīng)對(duì)象的方法, 將讀事件寫入到epoll
- //考慮下為什么要把這個(gè)回調(diào)設(shè)置在監(jiān)聽(tīng)對(duì)象上,而不是連接對(duì)象上。
- //因?yàn)槿绻?個(gè)客戶端連接上同一個(gè)監(jiān)聽(tīng)socket, 則會(huì)創(chuàng)建5個(gè)連接對(duì)象。而每一個(gè)連接對(duì)象都需要設(shè)置
- //這個(gè)回調(diào),占用4字節(jié)指針空間,浪費(fèi)內(nèi)存資源。而如果回調(diào)設(shè)置在監(jiān)聽(tīng)對(duì)象上,則只需要設(shè)置一次回調(diào)就可以了。
- ls->handler(c); //ngx_http_init_connection
-
- } while (ev->available);
- }
在函數(shù)中會(huì)調(diào)用ngx_listening_s對(duì)象的handler方法。這個(gè)方法其實(shí)就是ngx_http_init_connection,在ngx_http_add_listening函數(shù)中設(shè)置。
ngx_http_init_listening
---> ngx_http_add_listening
--->
- //創(chuàng)建一個(gè)ngx_listening_t對(duì)象,并給對(duì)象的成員賦值。例如設(shè)置監(jiān)聽(tīng)回調(diào)
- ngx_listening_t * ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
- {
- //創(chuàng)建一個(gè)ngx_listening_t對(duì)象
- ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);
-
- //監(jiān)聽(tīng)回調(diào)
- ls->handler = ngx_http_init_connection;
- }
ngx_http_init_connection是用來(lái)為新建立的客戶端連接注冊(cè)讀事件回調(diào)
ngx_http_init_request、寫事件回調(diào)
ngx_http_empty_handler、同時(shí)將注冊(cè)讀事件的超時(shí)事件到紅黑樹(shù)實(shí)現(xiàn)的定時(shí)器中。最終將讀事件放入到epoll中。這些操作執(zhí)行之后,就可以接收來(lái)自客戶端的數(shù)據(jù)了。
- //在收到客戶端連接時(shí),ngx_event_accept函數(shù)中會(huì)調(diào)用ngx_listening_t的handler,也就是本函數(shù)
- //功能:注冊(cè)客戶端的讀寫事件回調(diào)
- void ngx_http_init_connection(ngx_connection_t *c)
- {
- rev = c->read;
- //讀事件回調(diào)
- rev->handler = ngx_http_init_request;
-
- //該寫回調(diào)沒(méi)有做任何事件,因?yàn)檫@個(gè)階段還不需要向客戶端寫入任何數(shù)據(jù)
- c->write->handler = ngx_http_empty_handler;
-
- //將讀事件插入到紅黑樹(shù)中,用于管理超時(shí)事件,post_accept_timeout超時(shí)事件
- //為nginx.conf中的client_header_timeout選項(xiàng)
- ngx_add_timer(rev, c->listening->post_accept_timeout);
-
- //將讀事件注冊(cè)到epoll中,此時(shí)并沒(méi)有把寫事件注冊(cè)到epoll中,因?yàn)楝F(xiàn)在還不需要向客戶端發(fā)送任何數(shù)據(jù),所以寫事件并不需要注冊(cè)
- ngx_handle_read_event(rev, 0);
- }
nginx服務(wù)器處理完客戶端的連接請(qǐng)求后,又回到了work進(jìn)程的事件循環(huán)中。監(jiān)聽(tīng)新建立的對(duì)象,等待客戶端發(fā)來(lái)的數(shù)據(jù),與客戶端進(jìn)行數(shù)據(jù)交互。
- //work進(jìn)程的事件循環(huán)
- static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
- {
- for ( ;; )
- {
- ngx_process_events_and_timers(cycle);
- }
- }
二、超時(shí)事件管理 nginx服務(wù)器在監(jiān)聽(tīng)到來(lái)自客戶端的連接請(qǐng)求后,會(huì)與客戶端建立一個(gè)tcp連接,并為這個(gè)新連接注冊(cè)讀寫事件,并將讀事件的超時(shí)事件加入到紅黑樹(shù)實(shí)現(xiàn)的定時(shí)器中。
從上面的ngx_http_init_connection函數(shù)中就可以看出這些操作,并把讀事件添加到了紅黑樹(shù)實(shí)現(xiàn)的定時(shí)器中。
- //在收到客戶端連接時(shí),ngx_event_accept函數(shù)中會(huì)調(diào)用ngx_listening_t的handler,也就是本函數(shù)
- //功能:注冊(cè)客戶端的讀寫事件回調(diào)
- void ngx_http_init_connection(ngx_connection_t *c)
- {
- rev = c->read;
- //讀事件回調(diào)
- rev->handler = ngx_http_init_request;
-
- //該寫回調(diào)沒(méi)有做任何事件,因?yàn)檫@個(gè)階段還不需要向客戶端寫入任何數(shù)據(jù)
- c->write->handler = ngx_http_empty_handler;
-
- //將讀事件插入到紅黑樹(shù)中,用于管理超時(shí)事件,post_accept_timeout超時(shí)事件
- //為nginx.conf中的client_header_timeout選項(xiàng)
- ngx_add_timer(rev, c->listening->post_accept_timeout);
-
- //將讀事件注冊(cè)到epoll中
- ngx_handle_read_event(rev, 0);
- }
ngx_event_add_timer負(fù)責(zé)將事件注冊(cè)到紅黑樹(shù)實(shí)現(xiàn)的定時(shí)器中。紅黑樹(shù)中的所有超時(shí)事件節(jié)點(diǎn)都是通過(guò)ngx_event_s對(duì)象的timer成員給串接起來(lái)。而定時(shí)器中每一個(gè)超時(shí)事件節(jié)點(diǎn)的key就是超時(shí)時(shí)間,記錄該事件的超時(shí)時(shí)間。
- //將定時(shí)事件添加到紅黑樹(shù)中,timer為超時(shí)時(shí)間
- static ngx_inline void ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
- {
- ngx_msec_t key;
- ngx_msec_int_t diff;
-
- key = ngx_current_msec + timer;
-
- //已經(jīng)將事件插入到紅黑樹(shù)種,則先刪除之前的事件
- if (ev->timer_set)
- {
- ngx_del_timer(ev);
- }
-
- //設(shè)置定時(shí)器的唯一id,也就是時(shí)間
- ev->timer.key = key;
-
-
- //插入到紅黑樹(shù)種
- ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);
-
-
- //表示事件已經(jīng)存在紅黑樹(shù)中了
- ev->timer_set = 1;
- }
將讀事件加入到紅黑樹(shù)定時(shí)器后,接下來(lái)work進(jìn)程進(jìn)入事件循環(huán),阻塞在epoll_wait調(diào)用。那epoll_wait什么時(shí)候返回呢? 在接收到客戶端的數(shù)據(jù)后,或者每個(gè)事件的定時(shí)時(shí)間到后,可以從epoll_wait返回。接下來(lái)看下如何設(shè)置epoll_wait的超時(shí)時(shí)間,使得定時(shí)時(shí)間到后,能及時(shí)從epoll_wait返回。
- //work進(jìn)程事件循環(huán)
- void ngx_process_events_and_timers(ngx_cycle_t *cycle)
- {
- //在紅黑樹(shù)中查找所有事件的最小超時(shí)事件,返回值timer就是所有事件的最小超時(shí)時(shí)間
- timer = ngx_event_find_timer();
-
- //調(diào)用epoll_wait等待事件
- (void) ngx_process_events(cycle, timer, flags);
-
- //epoll_wait返回后,處理所有超時(shí)事件
- ngx_event_expire_timers();
- }
紅黑樹(shù)是一顆二叉排序樹(shù),因此最小超時(shí)時(shí)間實(shí)際上就是左子樹(shù)的最小值。因此可以看到ngx_event_find_timer函數(shù)的實(shí)現(xiàn),就是在左子樹(shù)種查找最小值。如不清楚紅黑樹(shù)的實(shí)現(xiàn),則可以查看july大神的博客http://www.cnblogs.com/v-July-v/archive/2010/12/29/1983707.html
- //返回紅黑樹(shù)中最小事件的超時(shí)事件;
- //返回值:>0 表示還剩多長(zhǎng)事件超時(shí)
- // <=0 表示事件已經(jīng)超時(shí)
- ngx_msec_t ngx_event_find_timer(void)
- {
- ngx_msec_int_t timer;
- ngx_rbtree_node_t *node, *root, *sentinel;
-
- //紅黑樹(shù)為空,則返回-1表示事件已經(jīng)超時(shí)
- if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel)
- {
- return NGX_TIMER_INFINITE;
- }
- root = ngx_event_timer_rbtree.root;
- sentinel = ngx_event_timer_rbtree.sentinel;
- //查找左字?jǐn)?shù)
- node = ngx_rbtree_min(root, sentinel);
-
- //計(jì)算剩余超時(shí)時(shí)間
- timer = (ngx_msec_int_t) node->key - (ngx_msec_int_t) ngx_current_msec;
-
- return (ngx_msec_t) (timer > 0 ? timer : 0);
- }
而epoll_wait調(diào)用返回后,如果有事件超時(shí)了,那如何處理這些超時(shí)事件呢?ngx_event_expire_timers內(nèi)部會(huì)遍歷紅黑樹(shù),查找所有已經(jīng)超時(shí)的事件,并調(diào)用這些超時(shí)事件的處理回調(diào)。需要注意的是,函數(shù)也會(huì)從紅黑樹(shù)中刪除這個(gè)超時(shí)事件,因此如果還需要管理這個(gè)超時(shí)事件,則需要重新把事件添加到紅黑樹(shù)實(shí)現(xiàn)的定時(shí)器中。
- //調(diào)用紅黑樹(shù)中所有已經(jīng)超時(shí)的事件回調(diào),并把已經(jīng)超時(shí)的事件從紅黑樹(shù)中刪除
- void ngx_event_expire_timers(void)
- {
- ngx_event_t *ev;
- ngx_rbtree_node_t *node, *root, *sentinel;
-
- sentinel = ngx_event_timer_rbtree.sentinel;
- //遍歷紅黑樹(shù),查找超時(shí)事件
- for ( ;; )
- {
- root = ngx_event_timer_rbtree.root;
- //紅黑樹(shù)為空則返回
- if (root == sentinel)
- {
- return;
- }
-
- //取出紅黑樹(shù)中時(shí)間最小的節(jié)點(diǎn)
- node = ngx_rbtree_min(root, sentinel);
-
- /* node->key <= ngx_current_time */
- //發(fā)生超時(shí)
- if ((ngx_msec_int_t) node->key - (ngx_msec_int_t) ngx_current_msec <= 0)
- {
- ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
-
- //從紅黑樹(shù)中刪除這個(gè)已經(jīng)超時(shí)的定時(shí)器事件
- ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
-
- //表示事件已經(jīng)不存在定時(shí)器中了
- ev->timer_set = 0;
-
- //標(biāo)示事件已經(jīng)超時(shí)
- ev->timedout = 1;
-
- //調(diào)用事件回調(diào)
- ev->handler(ev);
-
- //直接處理下一個(gè)超時(shí)事件,前一個(gè)超時(shí)事件已經(jīng)從紅黑樹(shù)中刪除了
- continue;
- }
-
- //沒(méi)有事件超時(shí)則直接退出,因此最小時(shí)間都沒(méi)有超時(shí),那紅黑樹(shù)中其它時(shí)間也肯定沒(méi)有超時(shí)
- break;
- }
- }
到此,超時(shí)事件的管理也分析完成了。