国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
nginx事件模塊之客戶端連接與超時(shí)管理

        上一篇文章分析了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ù)交互。

  1. //ngx_event_core_module模塊的init_process方法。在函數(shù)ngx_worker_process_init中被調(diào)用  
  2. static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)  
  3. {  
  4.     //對(duì)于每一個(gè)監(jiān)聽(tīng)端口,從連接池中取出一個(gè)連接對(duì)象(也將從讀時(shí)間,寫事件池取出對(duì)象,  
  5.     //使得連接,讀、寫保持一一對(duì)應(yīng)關(guān)系),負(fù)責(zé)監(jiān)聽(tīng)來(lái)自客戶端的連接  
  6.     ls = cycle->listening.elts;  
  7.     for (i = 0; i < cycle->listening.nelts; i++)   
  8.     {  
  9.         c = ngx_get_connection(ls[i].fd, cycle->log);  
  10.         //建立連接對(duì)象與監(jiān)聽(tīng)對(duì)象的關(guān)系  
  11.         c->listening = &ls[i];  
  12.         //建立監(jiān)聽(tīng)對(duì)象與連接對(duì)象的關(guān)系  
  13.         ls[i].connection = c;  
  14.         rev = c->read;  
  15.   
  16.         //設(shè)置連接回調(diào),當(dāng)有客戶端連接時(shí),將觸發(fā)回調(diào)  
  17.         rev->handler = ngx_event_accept;  
  18.           
  19.         //如果work進(jìn)程之間沒(méi)有使用枷鎖,則把讀事件加入epoll中  
  20.         //此時(shí)寫事件的回調(diào)為NULL,因?yàn)樵趎gx_get_connection函數(shù)中會(huì)把整個(gè)結(jié)構(gòu)進(jìn)行清0操作  
  21.         if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR)   
  22.         {  
  23.             return NGX_ERROR;  
  24.         }  
  25. }  

        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ù)讀寫。

  1. //客戶端請(qǐng)求連接回調(diào)  
  2. void ngx_event_accept(ngx_event_t *ev)  
  3. {  
  4.     do   
  5.     {  
  6.         //接收客戶端連接  
  7.         s = accept(lc->fd, (struct sockaddr *) sa, &socklen);  
  8.   
  9.         //work進(jìn)程之間賦值均衡,但一個(gè)work進(jìn)程超過(guò)每一個(gè)進(jìn)程的最大連接數(shù)的7/8時(shí),  
  10.         //則該work進(jìn)程不在監(jiān)聽(tīng)來(lái)自客戶端的連接請(qǐng)求。但已經(jīng)建立tcp連接的客戶端不收影響,正常進(jìn)行數(shù)據(jù)讀寫  
  11.         ngx_accept_disabled = ngx_cycle->connection_n / 8  
  12.                               - ngx_cycle->free_connection_n;  
  13.   
  14.         //獲取一個(gè)空閑連接對(duì)象(同時(shí)也獲取到讀,寫事件)  
  15.         c = ngx_get_connection(s, ev->log);  
  16.   
  17.         //給新連接對(duì)象賦值  
  18.         c->pool = ngx_create_pool(ls->pool_size, ev->log);  
  19.         c->sockaddr = ngx_palloc(c->pool, socklen);  
  20.         ngx_memcpy(c->sockaddr, sa, socklen);  
  21.   
  22.         //設(shè)置從內(nèi)核讀取數(shù)據(jù),寫入數(shù)據(jù)的的公共方法。這些方法實(shí)際上就是ngx_os_io結(jié)構(gòu)的各個(gè)成員  
  23.         //這些方法為什么不設(shè)置在事件對(duì)象上,而是設(shè)置在連接對(duì)象。因?yàn)檫@對(duì)讀寫事件而言,這些方法是公共的。  
  24.         //連接對(duì)象里面包含了讀寫事件對(duì)象的引用關(guān)系,如果設(shè)置在相應(yīng)的讀事件,或者寫事件上,則每個(gè)事件都需要設(shè)置一次  
  25.         //而在連接對(duì)象上只需要設(shè)置一次  
  26.         c->recv = ngx_recv;  
  27.         c->send = ngx_send;  
  28.         c->recv_chain = ngx_recv_chain;  
  29.         c->send_chain = ngx_send_chain;  
  30.           
  31.         //連接對(duì)象里面的監(jiān)聽(tīng)指針指向ls,但ls并沒(méi)有把連接指向當(dāng)前已經(jīng)調(diào)用accept的這個(gè)連接,  
  32.         //而是指向監(jiān)聽(tīng)連接對(duì)象  
  33.         c->listening = ls;  
  34.   
  35.         //調(diào)用監(jiān)聽(tīng)對(duì)象的方法, 將讀事件寫入到epoll  
  36.         //考慮下為什么要把這個(gè)回調(diào)設(shè)置在監(jiān)聽(tīng)對(duì)象上,而不是連接對(duì)象上。  
  37.         //因?yàn)槿绻?個(gè)客戶端連接上同一個(gè)監(jiān)聽(tīng)socket, 則會(huì)創(chuàng)建5個(gè)連接對(duì)象。而每一個(gè)連接對(duì)象都需要設(shè)置  
  38.         //這個(gè)回調(diào),占用4字節(jié)指針空間,浪費(fèi)內(nèi)存資源。而如果回調(diào)設(shè)置在監(jiān)聽(tīng)對(duì)象上,則只需要設(shè)置一次回調(diào)就可以了。  
  39.         ls->handler(c);      //ngx_http_init_connection  
  40.   
  41.     } while (ev->available);  
  42. }  

        在函數(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

           --->

  1. //創(chuàng)建一個(gè)ngx_listening_t對(duì)象,并給對(duì)象的成員賦值。例如設(shè)置監(jiān)聽(tīng)回調(diào)  
  2. ngx_listening_t * ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)  
  3. {  
  4.     //創(chuàng)建一個(gè)ngx_listening_t對(duì)象  
  5.     ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);  
  6.       
  7.     //監(jiān)聽(tīng)回調(diào)  
  8.     ls->handler = ngx_http_init_connection;  
  9. }  
        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ù)了。

  1. //在收到客戶端連接時(shí),ngx_event_accept函數(shù)中會(huì)調(diào)用ngx_listening_t的handler,也就是本函數(shù)  
  2. //功能:注冊(cè)客戶端的讀寫事件回調(diào)  
  3. void ngx_http_init_connection(ngx_connection_t *c)  
  4. {  
  5.     rev = c->read;  
  6.     //讀事件回調(diào)  
  7.     rev->handler = ngx_http_init_request;                  
  8.   
  9.     //該寫回調(diào)沒(méi)有做任何事件,因?yàn)檫@個(gè)階段還不需要向客戶端寫入任何數(shù)據(jù)  
  10.     c->write->handler = ngx_http_empty_handler;             
  11.   
  12.     //將讀事件插入到紅黑樹(shù)中,用于管理超時(shí)事件,post_accept_timeout超時(shí)事件  
  13.     //為nginx.conf中的client_header_timeout選項(xiàng)  
  14.     ngx_add_timer(rev, c->listening->post_accept_timeout);  
  15.   
  16.     //將讀事件注冊(cè)到epoll中,此時(shí)并沒(méi)有把寫事件注冊(cè)到epoll中,因?yàn)楝F(xiàn)在還不需要向客戶端發(fā)送任何數(shù)據(jù),所以寫事件并不需要注冊(cè)  
  17.     ngx_handle_read_event(rev, 0);  
  18. }  
        nginx服務(wù)器處理完客戶端的連接請(qǐng)求后,又回到了work進(jìn)程的事件循環(huán)中。監(jiān)聽(tīng)新建立的對(duì)象,等待客戶端發(fā)來(lái)的數(shù)據(jù),與客戶端進(jìn)行數(shù)據(jù)交互。

  1. //work進(jìn)程的事件循環(huán)  
  2. static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)  
  3. {  
  4.     for ( ;; )   
  5.     {  
  6.         ngx_process_events_and_timers(cycle);  
  7.     }  
  8. }  
二、超時(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í)器中。

  1. //在收到客戶端連接時(shí),ngx_event_accept函數(shù)中會(huì)調(diào)用ngx_listening_t的handler,也就是本函數(shù)  
  2. //功能:注冊(cè)客戶端的讀寫事件回調(diào)  
  3. void ngx_http_init_connection(ngx_connection_t *c)  
  4. {  
  5.     rev = c->read;  
  6.     //讀事件回調(diào)  
  7.     rev->handler = ngx_http_init_request;                  
  8.   
  9.     //該寫回調(diào)沒(méi)有做任何事件,因?yàn)檫@個(gè)階段還不需要向客戶端寫入任何數(shù)據(jù)  
  10.     c->write->handler = ngx_http_empty_handler;             
  11.   
  12.     //將讀事件插入到紅黑樹(shù)中,用于管理超時(shí)事件,post_accept_timeout超時(shí)事件  
  13.     //為nginx.conf中的client_header_timeout選項(xiàng)  
  14.     ngx_add_timer(rev, c->listening->post_accept_timeout);  
  15.   
  16.     //將讀事件注冊(cè)到epoll中  
  17.     ngx_handle_read_event(rev, 0);  
  18. }  

        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í)間。

  1. //將定時(shí)事件添加到紅黑樹(shù)中,timer為超時(shí)時(shí)間  
  2. static ngx_inline void ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)  
  3. {  
  4.     ngx_msec_t      key;  
  5.     ngx_msec_int_t  diff;  
  6.   
  7.     key = ngx_current_msec + timer;  
  8.   
  9.     //已經(jīng)將事件插入到紅黑樹(shù)種,則先刪除之前的事件  
  10.     if (ev->timer_set)   
  11.     {  
  12.         ngx_del_timer(ev);  
  13.     }  
  14.   
  15.     //設(shè)置定時(shí)器的唯一id,也就是時(shí)間  
  16.     ev->timer.key = key;  
  17.   
  18.   
  19.     //插入到紅黑樹(shù)種  
  20.     ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);  
  21.   
  22.   
  23.     //表示事件已經(jīng)存在紅黑樹(shù)中了  
  24.     ev->timer_set = 1;  
  25. }  

        將讀事件加入到紅黑樹(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返回。

  1. //work進(jìn)程事件循環(huán)  
  2. void ngx_process_events_and_timers(ngx_cycle_t *cycle)  
  3. {  
  4.     //在紅黑樹(shù)中查找所有事件的最小超時(shí)事件,返回值timer就是所有事件的最小超時(shí)時(shí)間  
  5.     timer = ngx_event_find_timer();  
  6.   
  7.     //調(diào)用epoll_wait等待事件  
  8.     (void) ngx_process_events(cycle, timer, flags);  
  9.       
  10.     //epoll_wait返回后,處理所有超時(shí)事件  
  11.     ngx_event_expire_timers();  
  12. }  

        紅黑樹(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

  1. //返回紅黑樹(shù)中最小事件的超時(shí)事件;  
  2. //返回值:>0 表示還剩多長(zhǎng)事件超時(shí)  
  3. //       <=0 表示事件已經(jīng)超時(shí)  
  4. ngx_msec_t ngx_event_find_timer(void)  
  5. {  
  6.     ngx_msec_int_t      timer;  
  7.     ngx_rbtree_node_t  *node, *root, *sentinel;  
  8.   
  9.     //紅黑樹(shù)為空,則返回-1表示事件已經(jīng)超時(shí)  
  10.     if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel)   
  11.     {  
  12.         return NGX_TIMER_INFINITE;  
  13.     }  
  14.     root = ngx_event_timer_rbtree.root;  
  15.     sentinel = ngx_event_timer_rbtree.sentinel;  
  16.     //查找左字?jǐn)?shù)  
  17.     node = ngx_rbtree_min(root, sentinel);  
  18.   
  19.     //計(jì)算剩余超時(shí)時(shí)間  
  20.     timer = (ngx_msec_int_t) node->key - (ngx_msec_int_t) ngx_current_msec;  
  21.   
  22.     return (ngx_msec_t) (timer > 0 ? timer : 0);  
  23. }  
        而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í)器中。
  1. //調(diào)用紅黑樹(shù)中所有已經(jīng)超時(shí)的事件回調(diào),并把已經(jīng)超時(shí)的事件從紅黑樹(shù)中刪除  
  2. void ngx_event_expire_timers(void)  
  3. {  
  4.     ngx_event_t        *ev;  
  5.     ngx_rbtree_node_t  *node, *root, *sentinel;  
  6.   
  7.     sentinel = ngx_event_timer_rbtree.sentinel;  
  8.     //遍歷紅黑樹(shù),查找超時(shí)事件  
  9.     for ( ;; )   
  10.     {  
  11.         root = ngx_event_timer_rbtree.root;  
  12.         //紅黑樹(shù)為空則返回  
  13.         if (root == sentinel)  
  14.         {  
  15.             return;  
  16.         }  
  17.   
  18.         //取出紅黑樹(shù)中時(shí)間最小的節(jié)點(diǎn)  
  19.         node = ngx_rbtree_min(root, sentinel);  
  20.   
  21.         /* node->key <= ngx_current_time */  
  22.         //發(fā)生超時(shí)  
  23.         if ((ngx_msec_int_t) node->key - (ngx_msec_int_t) ngx_current_msec <= 0)  
  24.         {  
  25.             ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));  
  26.   
  27.             //從紅黑樹(shù)中刪除這個(gè)已經(jīng)超時(shí)的定時(shí)器事件  
  28.             ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);  
  29.   
  30.             //表示事件已經(jīng)不存在定時(shí)器中了  
  31.             ev->timer_set = 0;  
  32.   
  33.             //標(biāo)示事件已經(jīng)超時(shí)  
  34.             ev->timedout = 1;  
  35.   
  36.             //調(diào)用事件回調(diào)  
  37.             ev->handler(ev);  
  38.   
  39.             //直接處理下一個(gè)超時(shí)事件,前一個(gè)超時(shí)事件已經(jīng)從紅黑樹(shù)中刪除了  
  40.             continue;  
  41.         }  
  42.   
  43.         //沒(méi)有事件超時(shí)則直接退出,因此最小時(shí)間都沒(méi)有超時(shí),那紅黑樹(shù)中其它時(shí)間也肯定沒(méi)有超時(shí)  
  44.         break;  
  45.     }  
  46. }  
        到此,超時(shí)事件的管理也分析完成了。


本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
“驚群”,看看nginx是怎么解決它的
nginx源碼分析之事件機(jī)制
Nginx多進(jìn)程并發(fā)連接處理模型 | DCCMX
聊聊 Node.js 的底層原理|應(yīng)用程序|加載器|node.js|調(diào)用|代碼
第119課:LTE-DRX工作原理及機(jī)制
第三章 LTE MAC協(xié)議解讀
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服